Animal-vectored nutrient flow along different resource gradients influences the nature of local and meta-ecosystem functioning
This notebook contains the code used to numerically analyze the behaviour of a meta-ecosystem model in which consumers move across ecosystem (or ‘patches’) in both diffusive and non-diffusive fashion. Here, we use diffusive and non-diffusive to indicate movement that happens either along or against a natural resource gradient, respectively—e.g., nutrient availability.
When they leave an ecosystem, organisms do not immediately enter another one, but rather spend a significant amount of time looking for a suitable one (Weisser and Hassell 1996). Models of organismal movement in meta-ecosystems should account for this time spent outside ecosystem borders, in the so-called ‘unsuitable matrix’ (Gounand et al. 2018), as it may leads to loss of biomass and nutrients from the system.
We model this type of movement by adopting the framework developed by Weisser and Hassell (1996). Briefly, in their model of host-parasitoid dynamics, Weisser and Hassell (1996) introduce a dispersers’ pool connecting two ecosystems, so that individuals move first to the dispersers’ pool and then to a different ecosystem from there. This approach has the advantage of decoupling the local ecosystem dynamics from the movement process—effectively making them two separate events. Furthermore, these separate events happen at different times scales, as basic plant and wildlife biology tells us that plant growth takes place over the course of months to years, whereas movement of animals can happen on the scale of minutes to hours. Hence, to analyze this new model, we use time scales separation, a mathematical technique that explicitly accounts for difference in the speed of different ecological processes by letting fast ones reach a quasi-equilibrium while holding slow ones constant, and then plug this quasi-equilibrium into the slow processes’ equations (Otto and Day 2011).
Here, we simulate the behaviour of a system comprising 2 ecosystems connected by unidirectional movement between the two consumers compartments, mediated by a dispersers’ pool. The model includes a simple version of recycling within each ecosystem.
The table below lists the model’s state variables and parameters.
| Variable | Description | Units | Range |
|---|---|---|---|
| Ni | Nutrients stocks in patch i | g | >0 |
| Pi | Producers stocks in patch i | g | >0 |
| Ci | Consumers stocks in patch i | g | >0 |
| Q | Dispersers’ Pool | g | >0 |
| Parameter | Description | Units | Range |
|---|---|---|---|
| Ii | Inorganic nutrient input rate into patch i | g*t-1 | [0,20] |
| l | Inorganic nutrient output rate | t-1 | [0,10] |
| ui | Producer uptake rate in patch i | (g*t)-1 | [0,10] |
| ai | Consumer attack rate in patch i | (g*t)-1 | [0,10] |
| \(\epsilon\)i | Consumer assimilation efficiency in patch i | unitless | [0,1] |
| hi | Biomass loss rate from Pi | t-1 | [0,10] |
| di | Biomass loss rate from Ci | t-1 | [0,10] |
| g | Movement rate from Ci to Q | t-1 | [0,10] |
| m | Movement rate from Q to Ci | t-1 | [0,10] |
| c | Biomass loss rate from Q | t-1 | [0,10] |
The model has a single feasible equilibrium, in which all state variables are >0, and three unfeasible equilibria. These were calculated using software Mathematica (Wolfram Research Inc. 2020) and are shown in Appendix A of the paper. In this document, we work uniquely with the feasible equilibrium (equations 1–7 in the paper).
This notebook was compiled on a machine using the following version of R and necessary packages:
R version 4.0.4 (2021-02-15)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Catalina 10.15.7
attached base packages:
[1] stats graphics grDevices utils datasets methods
[7] base
other attached packages:
[1] xaringanExtra_0.5.5 khroma_1.7.0 scales_1.1.1
[4] gt_0.3.0.9000 patchwork_1.1.1 ggpol_0.0.7
[7] ggpubr_0.4.0 lhs_1.1.1 plotrix_3.8-1
[10] forcats_0.5.1 stringr_1.4.0 dplyr_1.0.5
[13] purrr_0.3.4 readr_1.4.0 tidyr_1.1.3
[16] tibble_3.1.0 ggplot2_3.3.5 tidyverse_1.3.0
[19] rmarkdown_2.7
loaded via a namespace (and not attached):
[1] fs_1.5.0 usethis_2.0.1 lubridate_1.7.10
[4] devtools_2.3.2 httr_1.4.2 rprojroot_2.0.2
[7] tools_4.0.4 backports_1.2.1 bslib_0.2.4
[10] utf8_1.2.1 R6_2.5.0 DBI_1.1.1
[13] colorspace_2.0-0 withr_2.4.1 tidyselect_1.1.0
[16] prettyunits_1.1.1 tictoc_1.0 processx_3.4.5
[19] downlit_0.2.1 curl_4.3 compiler_4.0.4
[22] cli_2.3.1 rvest_1.0.0 formatR_1.8
[25] xml2_1.3.2 desc_1.3.0 sass_0.3.1
[28] callr_3.5.1 digest_0.6.27 foreign_0.8-81
[31] rio_0.5.26 pkgconfig_2.0.3 htmltools_0.5.1.1
[34] sessioninfo_1.1.1 dbplyr_2.1.0 fastmap_1.1.0
[37] rlang_0.4.10 readxl_1.3.1 rstudioapi_0.13
[40] jquerylib_0.1.3 generics_0.1.0 jsonlite_1.7.2
[43] zip_2.1.1 car_3.0-10 distill_1.2
[46] magrittr_2.0.1 Rcpp_1.0.6 munsell_0.5.0
[49] fansi_0.4.2 abind_1.4-5 lifecycle_1.0.0
[52] stringi_1.5.3 yaml_2.2.1 carData_3.0-4
[55] easypackages_0.1.0 plyr_1.8.6 pkgbuild_1.2.0
[58] grid_4.0.4 crayon_1.4.2 haven_2.3.1
[61] hms_1.1.1 knitr_1.31 ps_1.6.0
[64] pillar_1.5.1 ggsignif_0.6.1 pkgload_1.2.0
[67] reprex_1.0.0 glue_1.4.2 evaluate_0.14
[70] data.table_1.14.0 remotes_2.2.0 modelr_0.1.8
[73] vctrs_0.3.8 testthat_3.0.2 cellranger_1.1.0
[76] gtable_0.3.0 assertthat_0.2.1 cachem_1.0.4
[79] openxlsx_4.2.3 xfun_0.26 broom_0.7.5
[82] rstatix_0.7.0 memoise_2.0.0 ellipsis_0.3.2
The code chunk below loads the necessary packages and prints information on the current R session (uncomment the relavant line of code to show these information).
library(rmarkdown) # build interactive documents in R
library(tidyverse) # collection of packages improving base R
library(plotrix) # additional plotting functions
library(lhs) # use latin hypercube to create toy datasets
library(ggpubr) # extra themes for ggplot2
library(ggpol) # extra geoms for ggplot2
library(patchwork) # easily create and layout multi-panel plots
library(gt) # building tables in a tidyverse framework
library(scales) # scaling functions for ggplot2
library(khroma) # color-blind palettes for ggplot2
library(xaringanExtra) # extra tabbing functionality for rmarkdown documents
# utils:::print.sessionInfo(sessionInfo())
Let’s begin by generating two sets of randomly-drawn parameter values, one for parameters with values scaled between 0 and 10 (DATA1) and another one for parameters with values scaled between 0 and 1 (DATA2). Note that we will use these randomly-drawn values for all simulations in this document.
# Below we generate random values for those parameters that will vary
# during the simulations: attack rates, efficiency, death rates, and
# movement rates
DATA1 <- NULL
# Run 2000 iterations of 100 numbers (in 100 bins between 0 to 1)
for (i in seq(1, 100, 1)) {
# Use lhs function in R. 100 bins, 11 iterations - one for each
# parameter that is scaled between 0 and 10.
loop_1 <- randomLHS(100, 11)
# Use rbind to stack each iteration on top of the other to create a one
# column list of 100000 samples with equal distribution in 100 bins.
# Multiplied by 10 to get range of data from 0 to 10
DATA1 <- rbind(DATA1, data.frame(i = i, Result = loop_1 * 10))
# print the i value so you can track progress print(i) Close the loop
}
# This is for 2 parameters that is scaled 0 to 1.
DATA2 <- NULL
# Run 2000 iterations of 100 numbers (in 100 bins between 0 to 1)
for (i in seq(1, 100, 1)) {
# Use lhs function in R. 100 bins, 2 iterations.
loop_2 <- randomLHS(100, 2)
# Use rbind to stack each iteration on top of the other to create a one
# column list of 200000 samples with equal distribution in 100 bins.
# Multiplied by 10 to get range of data from 0 to 10
DATA2 <- rbind(DATA2, data.frame(i = i, Result = loop_2))
# print the i value so you can track progress print(i) Close the loop
}
# Save these two datasets for later re-use write.csv(DATA1, file =
# '../Data/DATA1.csv', row.names = FALSE) write.csv(DATA2, file =
# '../Data/DATA2.csv', row.names = FALSE)
Alternatively, we can load the two datasets provided with this R notebook, in folder ../Data/. These were similarly generated using function lhs(), scaled either 0–10 or 0–1, and then used to produce the results and figures presented in the paper.
Note that loading the provided datasets is the default approach. If you would like to generate new randomly drawn parameter values, please turn off the code chunk below by setting
eval=FALSEand turn on the one above by settingeval=TRUEin the code chunk options.
Now, let’s allocate an empty dataframe to contain the data that we will draw from the two datasets produced above at each iteration. This dataframe will also host the resulting state variable biomass values, the nutrient flux values, and the trophic compartment productivity values. We will name it PRED.
# Allocate an empty data frame to save the data in
PRED <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PRED <- rbind(PRED, data.frame(TIME = seq(0, 100, 1), I1 = seq(0, 100,
1), I2 = seq(0, 100, 1), l = seq(0, 100, 1), u1 = seq(0, 100, 1), u2 = seq(0,
100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0, 100,
1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0, 100, 1),
g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100, 1), e1 = seq(0,
100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100, 1), P1 = seq(0,
100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100, 1), P2 = seq(0,
100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1), FLUX_P1 = seq(0,
100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0, 100, 1), FLUX_C2 = seq(0,
100, 1), FLUX_Eco_1 = seq(0, 100, 1), FLUX_Eco_2 = seq(0, 100,
1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0, 100, 1), PROD_C1 = seq(0,
100, 1), PROD_P2 = seq(0, 100, 1), PROD_C2 = seq(0, 100, 1)))
Now for the simulations. The code chunk below contains a for loop that, over a 10000 time steps, iterates the model’s equilibria to calculate stocks of Nutrients (N1, N2), Producers (P1, P2), and Consumers (C1, C2) in the two ecosystems. As well, it calculates the stock for the Dispersers’ Pool (Q), the recycling flux for each biotic compartment in both ecosystems (FLUX_X, where X is the compartment), and local and meta-ecosystem total recycling flux (FLUX_Eco_1, FLUX_Eco_2, MetaEcoFlux). Similarly, it calculates each trophic compartment’s productivity (PROD_X, where X is the trophic compartment).
At each time step, we draw a random value for each parameter listed in the above table from either the DATA1 or DATA2 datasets—depending on whether the parameter is scaled 0–10 or 0–1, respectively. Then, we plug these values into the model’s equilibria and recycling flux formulas as derived using Mathematica, and finally populate rows of values in PRED with the randomly drawn parameter values and trophic compartment stock values thus calculated.
Note that the feasibility conditions for this model are too complex to resolve analytically. Hence, we are not checking that the randomly-drawn parameters values satisfy them.
After running the simulations’ code, we calculate meta-ecosystem productivity for each trophic compartment. That is, the sum of a trophic level’s biomass production in ecosystem 1 and of its counterpart in ecosystem 2. These quantities, Ntot, Ptot, and Ctot, provide clues as to whether any effect of consumer movement in the local ecosystems can also be detected when considering the meta-ecosystem as a whole. Furthermore, we manually calculate each ecosystem’s own total recycling flux as the sum of the flux of its 2 biotic compartments, as a check on the Mathematica-derived formulas. We also calculate the meta-ecosystem flux (MetaEcoFlux) as the sum of the two ecosystems’ total flux. Finally, we calculate each trophic compartment’s total productivity across the two ecosystems (variables PROD_Ptot and PROD_Ctot).
These simulations include fixed values for the nutrient input rate in ecosystem 1 (I1), ecosystem 2 (I2), and the leaching rate for the two ecosystems (l). In this first batch of iterations, we will work with equal values of nutrient input and loss rates in the two ecosystems. We refer to this as a baseline scenario in later sections of this document, where we present results obtained from varying the values of I1, I2, and other parameters in the model.
Note that we refer interchangeably to
I1andI2as either the nutrient input rates or the environmental fertility of the two ecosystems
# Here we set values for parameters that we will not vary during the
# simulations Nutrients input rate in each patch
I1 = 10
I2 = 10
# Patch leaching rate/soil loss rate
l = 0.1
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above
# producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate)
h1 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystems 1
h2 = DATA1[i1, 7] # death (=recycling) rate for producers in ecosystem 2
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PRED[i1, ] <- c(i1, I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m,
c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 * e1 * I1)/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), (d1 + g)/(a1 * e1), (e1 * (-h1 *
l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (-a1 *
e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 *
(-1 + e2) * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 *
(e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 *
e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 +
e2) * u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 * e1 *
I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 + e1 * g *
m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 * l * (c + m) *
(h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l *
(c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 *
I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))) * u2), ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (e1 * g * (-h1 * l + I1 * u1))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) *
h1)/(a1 * e1), (d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) -
g) * h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 *
u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 *
(c + m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 * h2 *
l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), (d2 * ((e1 * g * m * (-h1 * l + I1 *
u1) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), ((d1 + g) * h1)/(a1 * e1) + (d1 * e1 * (-h1 * l + I1 *
u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (h2 * (l * (-d2 *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 *
e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 * g * m * (-h1 *
l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 *
(-1 + e2) * u2), (c * e1 * g * (-h1 * l + I1 * u1))/((c + m) *
(a1 * e1 * l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * ((d1 -
d1 * e1 + g) * h1 + a1 * e1 * I1) * u1)/(a1 * e1 * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), (e1 * (d1 + g) * (-h1 * l +
I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), ((-a1 *
e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 *
(-1 + e2) * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 *
(e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l -
d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l * (c + m) * (a1 *
e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 *
l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 *
e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))) * u2)),
(e2 * l * (-a1 * d2 * e1 * h2 * l * (c + m) + d2 * (d1 * (-1 +
e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m * (h1 * l -
I1 * u1)) + d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) * u2)))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem biomass
PRED$Ntot <- PRED$N1 + PRED$N2
PRED$Ptot <- PRED$P1 + PRED$P2
PRED$Ctot <- PRED$C1 + PRED$C2
# now calculate recycling flux for the local ecosystem
PRED$FLUX_Eco1_check <- PRED$FLUX_P1 + PRED$FLUX_C1
PRED$FLUX_Eco2_check <- PRED$FLUX_P2 + PRED$FLUX_C2
# and the meta-ecosystem recycling flux
PRED$FLUX_Ptot <- PRED$FLUX_P1 + PRED$FLUX_P2
PRED$FLUX_Ctot <- PRED$FLUX_C1 + PRED$FLUX_C2
PRED$MetaEcoFlux <- PRED$FLUX_Eco_1 + PRED$FLUX_Eco_2 - PRED$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PRED <- dplyr::mutate(PRED, PROD_Ptot = PROD_P1 + PROD_P2, .after = "PROD_C2")
PRED <- dplyr::mutate(PRED, PROD_Ctot = PROD_C1 + PROD_C2, .after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will flag them in the PRED dataset.
PRED <- PRED %>% dplyr::mutate(., biosense = ifelse(N1 > 0 & P1 > 0 & C1 >
0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"), .after = MetaEcoFlux) %>%
dplyr::mutate_at(vars(biosense), factor)
Here we perform stability analyses to investigate the behaviour of the model at equilibrium.
We perform the stability analyses by first evaluating the Jacobian matrix for the model in Mathematica (Wolfram Research Inc. 2020). Then, we transfer the Jacobian’s partial derivatives in R, and fit the same sets of parameter values and equilibrium state variable values mentioned above to these expression. Finally, we evaluate stability of a given parameter set by checking whether the real part of the leading eigenvalue of the Jacobian is positive or negative (Otto and Day 2011). If the real part of the Jacobian’s leading eigenvalue is negative, the equilibrium is stable (Otto and Day 2011).
The code below was developed with help from Dr. Anne McLeod and Dr. Robert Buchkowski.
# create an empty dataframe
MathStab <- NULL
for (i in 1:nrow(PRED)) {
# fetch parameter values from the equilibrium simulations df
I1 = PRED$I1[i]
I2 = PRED$I2[i]
l = PRED$l[i]
u1 = PRED$u1[i]
u2 = PRED$u2[i]
a1 = PRED$a1[i]
a2 = PRED$a2[i]
h1 = PRED$h1[i]
h2 = PRED$h2[i]
d1 = PRED$d1[i]
d2 = PRED$d2[i]
g = PRED$g[i]
m = PRED$m[i]
c = PRED$c[i]
e1 = PRED$e1[i]
e2 = PRED$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PRED$N1[i]
dP1 = PRED$P1[i]
dC1 = PRED$C1[i]
dN2 = PRED$N2[i]
dP2 = PRED$P2[i]
dC2 = PRED$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 * dC1 *
e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l - dP2 * u2,
h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 - h2 + dN2 *
u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 * dC2 * e2, -d2 +
a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab <- rbind(MathStab, data.frame(TIME = i, I1, I2, l, u1, u2,
a1, a2, h1, h2, d1, d2, g, m, c, e1, e2, dN1, dP1, dC1, dN2, dP2,
dC2, EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEv = max(Re(base::eigen(Jacob)$values)), stable = stable, biosense = PRED$biosense[i]))
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
# browser()
}
MathStab$stable <- as.factor(MathStab$stable)
# separate unstable equilibria to work with later
MathStabUS <- subset(MathStab, MathStab$stable == "unstable")
1.6% of the stability analyses runs produced unstable results (i.e., 155 out of 10000 iterations). Are these also the same as those that return equilibria without biological sense—i.e., those where state variables values at equilibrium are <0?
biononsense <- subset(PRED, PRED$biosense == "no")
identical(as.numeric(MathStabUS[, "TIME"]), biononsense[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PRED.
Now, let’s exclude the unstable, biological nonsense parameter sets from the analyses below and from our results. We are also going to sample the dataset PRED, that contains the results of our model’s iterations, for 1000 random iterations results. We will store these in PRED_1k and use them in subsequent graphs and figures, for ease of visualization.
PREDpos <- filter(PRED, PRED$biosense == "yes" & PRED$stable == "stable")
# sample PREDpos to only use 1000 random simulation results
PRED_sample1000 <- PREDpos[sample(nrow(PREDpos), size = 1000), ]
PRED_1k <- droplevels(PRED_sample1000)
We now have a dataset containing 1000 variations of the model’s results (i.e., PRED_1k). In the chunk below, we we use boxplots to investigate how the presence of unidirectional, non-diffusive consumer movement influences the meta-ecosystem’s behaviour.
# pivot the dataset to longer format for boxplot plotting
PRED_biomass_long <- select(PRED_1k, N1:Q, Ntot:Ctot) %>% pivot_longer(., names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers Pool", "Meta-ecosystem"))))
PRED_biomass_long$Compartment <- as_factor(PRED_biomass_long$Compartment)
PRED_biomass_long$Ecosystem <- as_factor(PRED_biomass_long$Ecosystem)
comparts_medians <- PRED_biomass_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data = PRED_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.25, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 1: Biomass stock values at equilibrium for local and meta-ecosystem scale. Ecosystem 1 and 2 include a tri-trophic food chain (Nutrient, Producers, Consumers) each. Consumers move from ecosystem 1 to ecosystem 2 via a Dispersers’ Pool (Q). The two ecosystems and the dispersers’ pool together form the meta-ecosystem. Point-down triangles identify outliers; labels on top of the compartments’ boxplots report median values for the full set of simulations. Values on the y-axis are limited between [0, 100] to zoom in on the variation in the results.
#ggsave(filename = "../Results/ModelEq_base.png", device = "png", dpi = 600, height = 5)
The graph above show a classic trophic cascade, albeit one that acts across ecosystem borders. In ecosystem 2, the influx of consumers from ecosystem 1 increases the consumer biomass, in turn depressing the producers’ abundance and releasing the nutrient stock from the producers’ trophic pressure. Conversely, in ecosystem 1, consumers’ numbers fall steadily—due to the combined action of mortality and emigration towards ecosystem 2—thus releasing the producers from the trophic pressure exerted by consumers. In turn, this lets producers grow unchecked, depressing the nutrient stocks in ecosystem 1. We can see this as well by looking at the change of log10 biomass values for each trophic compartment with respect to the two movement parameters: g, the rate of consumer movement from ecosystem 1 to the dispersers’ pool Q, and m, the rate of consumer movement from the dispersers’ pool Q to ecosystem 2.
eco_levels <- c(N1 = "Nutrient", P1 = "Producers", C1 = "Consumers", N2 = "Nutrient",
P2 = "Producers", C2 = "Consumers", Q = "Dispersers", Ntot = "Nutrient",
Ptot = "Producers", Ctot = "Consumers")
# pivot the dataset to longer format for boxplot plotting
PRED_long <- pivot_longer(PRED_1k, names_to = "Compartment", values_to = "Stock",
cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(.,
Ecosystem = if_else(Compartment == "N1" | Compartment == "C1" | Compartment ==
"P1", "Donor", if_else(Compartment == "N2" | Compartment == "P2" |
Compartment == "C2", "Recipient", if_else(Compartment == "Q", "Dispersers",
"Meta-ecosystem"))))
PRED_long$Compartment <- as_factor(PRED_long$Compartment)
PRED_long$Ecosystem <- as_factor(PRED_long$Ecosystem)
ggplot(PRED_long, aes(g, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.15) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 2: Change in biomass of the two ecosystems’ compartment, the dispersers’ pool, and the meta-ecostystem with change in the value of the rate of immigration into the disperser pool (g)—i.e., the rate at which consumers leave ecosystem 1. Each facet presents results for a single trophic compartment, with color identifying levels of biological organization. Lines are regression lines with 95% CI (grey shaded areas), drawn using the full range of values. Note the base-10 logarithmic scale on the ordinates (y-axis).
ggplot(PRED_long, aes(m, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.15) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 3: Change in biomass of the two ecosystems’ compartment, the dispersers’ pool, and the meta-ecostystem with change in the value of the rate of emigration from the disperser pool (m).—i.e., the rate at which consumers enter ecosystem 2. All other specifications as in Figure 2.
ggplot(PRED_long, aes(c, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.15) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 4: Change in biomass of the two ecosystems’ compartment, the dispersers’ pool, and the meta-ecostystem with change in the value of the consumer death rate while in the dispersers’ pool (c). This could represent predation risk, but also environmental hazards and stochastic events such as crossing a dangerous linear feature of the landscape—e.g., a busy highway. All other specifications as in Figure 2.
After taking a look at the productivity of the system by checking stock graphs, let’s take a look at the system’s recycling flux. In the graphs below, we will start by taking a look at recycling flux values in each ecosystem and in the meta-ecosystem for each iteration of the model. First, we will extract the flux columns (see above) from the PRED_1k dataframe into a new one which we will call FluxPRED. We will then pivot this dataframe from wide to long format, using function pivot_longer from package tidyr.
FluxPRED <- PRED_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>%
tidyr::pivot_longer(., names_to = "Compartment", values_to = "Flux",
cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(., Scale = if_else(Compartment ==
"FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1",
"Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" |
Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>%
dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
Now, let’s take a look at a summary boxplot of recycling flux in the local and meta-ecosystem.
FluxPRED$Scale <- as.factor(FluxPRED$Scale)
FluxPRED$Compartment <- as.factor(FluxPRED$Compartment)
comparts_medians <- FluxPRED %>% dplyr::group_by(Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPRED %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
dplyr::mutate(., Scale = fct_relevel(Scale, c("Donor", "Recipient", "Meta-ecosystem"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.25, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Scale, scales = "free") + scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Ptot" = "Producers", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Ctot" = "Consumers", "FLUX_Eco_1" = "Donor", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
coord_cartesian(y = c(0, 500)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none", axis.title.x = element_blank())
Figure 5: Recycling flux at local and meta-ecosystem scales when consumers move from ecosystem 1 to 2. Note that, in this case, the two ecosystems have the same fertility—i.e., basal inorganic input values are the same. Point-down triangles identify outliers; labels on top of the compartments’ boxplots report median values for the full set of simulations. The y-axis is constrained between [0, 500] to show the variation in the results.
Here, we investigate the behaviour of the system when one of the two parameters regulating movement to/from the dispersers’ pool is set to 0. That is, when movement is still part of the system but does not connect the two ecosystems under scrutiny.
We begin with the case of m = 0, and g = [0, 10]. The chunk below performs the dataframe allocation and simulation, using parameter values drawn from DATA1 and DATA2 previously allocated. Results are shown in Figure 6 below.
# Allocate an empty data frame to save the data in
PREDm0 <- NULL
# Allocate some rows and columns. We will replace the current numbers with results below
# Data we are calculating are (bio)mass for soil nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses (Q)
PREDm0 <-rbind(PREDm0, data.frame(TIME=seq(0,100,1), I1 = I1, I2 = I2, l = l, u1 = seq(0,100,1), u2 = seq(0,100,1), a1 = seq(0,100,1), a2 = seq(0,100,1), h1 = seq(0,100,1), h2 = seq(0,100,1), d1 = seq(0,100,1), d2 = seq(0,100,1), g = seq(0,100,1), m = seq(0,100,1), c = seq(0,100,1), e1 = seq(0,100,1), e2 = seq(0,100,1), N1=seq(0,100,1), P1=seq(0,100,1), C1=seq(0,100,1), N2=seq(0,100,1), P2=seq(0,100,1), C2=seq(0,100,1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0,9999,1)){
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x times as defined by the i for loop above
# producers uptake rates
u1 = DATA1[i1,2] # in ecosystem 1
u2 = DATA1[i1,3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1,4]
a2 = DATA1[i1,5]
# death rates (= recycling rate)
h1 = DATA1[i1,6] # death (=recycling) rate for producers in ecosystems 1
h2 = DATA1[i1,7] # death (=recycling) rate for producers in ecosystem 2
d1 = DATA1[i1,8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1,9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1,10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1,11]
# movement rate from the Disperser's pool Q
m = 0
# efficiency of consumers
e1 = DATA2[i1,2] # in ecosystem 1
e2 = DATA2[i1,3] # in ecosystem 2
PREDm0[i1,] <- c(i1, I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
((d1 - d1*e1 + g)*h1 + a1*e1*I1)/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(d1 + g)/(a1*e1),
(e1*(-h1*l + I1*u1))/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(-a1*e1*(d2*(-1 + e2)*h2 - a2*e2*I2)*l*(c + m) + d2*(-1 + e2)*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*(e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1))))/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)*(a2*e2*l - d2*(-1 + e2)*u2)),
(l*(-d2*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*e1*g*m*(-h1*l + I1*u1)) + d2*(d1*e1*I2*(c + m)*u1 - (d1 + g)*I2*(c + m)*u1 + e1*g*m*(h1*l - I1*u1))*u2 + a1*d2*e1*l*(c + m)*(h2*l - I2*u2))/(a2*e2*h2*l*(c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1) - a2*(a1*e1*e2*I2*l*(c + m) + e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1)))*u2),
((e1*g*m*(-h1*l + I1*u1)*u2)/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)) + e2*(-h2*l + I2*u2))/(a2*e2*l - d2*(-1 + e2)*u2))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDm0$Ntot <- PREDm0$N1 + PREDm0$N2
PREDm0$Ptot <- PREDm0$P1 + PREDm0$P2
PREDm0$Ctot <- PREDm0$C1 + PREDm0$C2
# Equilibrium stocks less than or equal to 0 make no biological sense, so we
# will remove them from the PRED dataset.
PREDm0pos <- subset(PREDm0, N1>0 & P1>0 & C1>0 & N2>0 & P2>0 & C2>0)
# PREDm0_sample1000 <- PRED[sample(nrow(PREDm0pos),size=1000),]
# pivot the
PREDm0_long <- pivot_longer(PREDm0pos, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", "Meta-ecosystem")))
PREDm0_long$Compartment <- as_factor(PREDm0_long$Compartment)
PREDm0_long$Ecosystem <- as_factor(PREDm0_long$Ecosystem)
comparts_medians <- PREDm0_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data=PREDm0_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.25, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 6: Biomass stock values at equilibrium for local and meta-ecosystem scale, when the rate of movement from the dispersers’ pool (m) to ecosystem 2 is set to 0. This results in a smaller consumers stock in ecosystem 2, in turn allowing Producers to grow more biomass and depress Nutrient stocks. All other specifications as in Figure 1.
While the difference in the relative biomass stocks among the trophic compartments in Ecosystem 2 is small compared to Figure 1, there is no trophic cascade in Ecosystem 2 in this scenario and the system is strongly bottom-up controlled. Conversely, in Ecosystem 1, Consumers are still reduced to minimal abundance over time, while Producers are free to depress Nutrients and grow.
ggplot(PRED_long, aes(g, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.15) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 7: Change in biomass of the two ecosystems’ compartment, the dispersers’ pool, and the meta-ecostystem with change in the value of the rate of the rate of immigration into the disperser pool (g)—i.e., the rate at which consumers leave ecosystem 1. All other specifications as in Figure 2.
ggplot(PRED_long, aes(c, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.15) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 8: Change in biomass of the two ecosystems’ compartment, the dispersers’ pool, and the meta-ecostystem with change in the value of the rate of the consumer death rate while in the dispersers’ pool (c). All other specifications as in Figure 2.
Now, for the case in which Consumers from Ecosystem 1 do not emigrate to the Dispersers’ Pool. In this scenario, we will set g = 0 from the start of the simulations, and draw m values from DATA1 as all other parameters scaled \(\in [0,10]\). Results are shown in Figure 9 below.
# Allocate an empty data frame to save the data in
PREDg0 <- NULL
# Allocate some rows and columns. We will replace the current numbers with results below
# Data we are calculating are (bio)mass for soil nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses (Q)
PREDg0 <-rbind(PREDg0, data.frame(TIME=seq(0,100,1), I1 = I1, I2 = I2, l = l, u1 = seq(0,100,1), u2 = seq(0,100,1), a1 = seq(0,100,1), a2 = seq(0,100,1), h1 = seq(0,100,1), h2 = seq(0,100,1), d1 = seq(0,100,1), d2 = seq(0,100,1), g = seq(0,100,1), m = seq(0,100,1), c = seq(0,100,1), e1 = seq(0,100,1), e2 = seq(0,100,1), N1=seq(0,100,1), P1=seq(0,100,1), C1=seq(0,100,1), N2=seq(0,100,1), P2=seq(0,100,1), C2=seq(0,100,1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0,9999,1)){
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x times as defined by the i for loop above
# producers uptake rates
u1 = DATA1[i1,2] # in ecosystem 1
u2 = DATA1[i1,3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1,4]
a2 = DATA1[i1,5]
# death rates (= recycling rate)
h1 = DATA1[i1,6] # death (=recycling) rate for producers in ecosystems 1
h2 = DATA1[i1,7] # death (=recycling) rate for producers in ecosystem 2
d1 = DATA1[i1,8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1,9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1,10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = 0
# movement rate from the Disperser's pool Q
m = DATA1[i1,11]
# efficiency of consumers
e1 = DATA2[i1,2] # in ecosystem 1
e2 = DATA2[i1,3] # in ecosystem 2
PREDg0[i1,] <- c(i1, I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
((d1 - d1*e1 + g)*h1 + a1*e1*I1)/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(d1 + g)/(a1*e1),
(e1*(-h1*l + I1*u1))/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(-a1*e1*(d2*(-1 + e2)*h2 - a2*e2*I2)*l*(c + m) + d2*(-1 + e2)*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*(e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1))))/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)*(a2*e2*l - d2*(-1 + e2)*u2)),
(l*(-d2*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*e1*g*m*(-h1*l + I1*u1)) + d2*(d1*e1*I2*(c + m)*u1 - (d1 + g)*I2*(c + m)*u1 + e1*g*m*(h1*l - I1*u1))*u2 + a1*d2*e1*l*(c + m)*(h2*l - I2*u2))/(a2*e2*h2*l*(c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1) - a2*(a1*e1*e2*I2*l*(c + m) + e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1)))*u2),
((e1*g*m*(-h1*l + I1*u1)*u2)/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)) + e2*(-h2*l + I2*u2))/(a2*e2*l - d2*(-1 + e2)*u2))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDg0$Ntot <- PREDg0$N1 + PREDg0$N2
PREDg0$Ptot <- PREDg0$P1 + PREDg0$P2
PREDg0$Ctot <- PREDg0$C1 + PREDg0$C2
# Equilibrium stocks less than or equal to 0 make no biological sense, so we
# will remove them from the PRED dataset.
PREDg0pos <- subset(PREDg0, N1>0 & P1>0 & C1>0 & N2>0 & P2>0 & C2>0)
# PREDg0_sample1000 <- PRED[sample(nrow(PREDg0pos),size=1000),]
# pivot the
PREDg0_long <- pivot_longer(PREDg0pos, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", "Meta-ecosystem")))
PREDg0_long$Compartment <- as_factor(PREDg0_long$Compartment)
PREDg0_long$Ecosystem <- as_factor(PREDg0_long$Ecosystem)
comparts_medians <- PREDg0_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data=PREDg0_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.25, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.16, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 9: Biomass stock values at equilibrium for local and meta-ecosystem scale, when the rate of movement from ecosystem 1 to the dispersers’ pool (g) is set to 0. This establishes bottom-up control in both ecosystems. All other specifications as in Figure 1.
In this case, the lack of movement from the Consumer trophic compartment to the Dispersers’ Pools establishes a classic bottom-up control in both ecosystems. As there is no movement through the dispersers’ pool Q, we will not show scatterplots like Figures 2, 3, or 4 above.
ggplot(PREDg0_long, aes(m, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.1) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 10: Change in biomass of the two ecosystems’ and the meta-ecostystemtrophic compartments with change in the value of the rate of the rate of emigration from the disperser pool (m). NOte that Q is not shown as there is no immigration into it. All other specifications as in Figure 2.
ggplot(PREDg0_long, aes(c, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.1) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 11: Change in biomass of the two ecosystems’ and the meta-ecostystem trophic compartments with change in the value of the rate of the consumer death rate while in the dispersers’ pool (c). Note that Q is not shown as there is not immigration into it. All other specifications as in Figure 2.
g from a set of valuesHere we run the model using a set value of m (= 0) and drawing g from a set of [0,10] values. To do this, we will use a nested for loop. In the outer loop, we will sequentially draw a value for g. We will then use this value to run the model in the inner loop. Here, we create object gvar, to store the values for g: these are values between 0 and 10, in 0.5 increments.
gvar <- seq(0, 10, 0.5)
Now, we run the model.
# Allocate an empty data frame to save the data in
PREDgvar <- NULL
for (j in 1:length(gvar)) {
# movement rate to the Disperser's pool Q
g = gvar[j]
# movement rate from the Disperser's pool Q
m = 0
PREDtemp <- NULL
PREDtemp <- rbind(PREDtemp, data.frame(TIME=seq(0,100,1), I1 = I1, I2 = I2, l = l, u1 = seq(0,100,1), u2 = seq(0,100,1), a1 = seq(0,100,1), a2 = seq(0,100,1), h1 = seq(0,100,1), h2 = seq(0,100,1), d1 = seq(0,100,1), d2 = seq(0,100,1), g = seq(0,100,1), m = seq(0,100,1), c = seq(0,100,1), e1 = seq(0,100,1), e2 = seq(0,100,1), N1=seq(0,100,1), P1=seq(0,100,1), C1=seq(0,100,1), N2=seq(0,100,1), P2=seq(0,100,1), C2=seq(0,100,1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0,199,1)){
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x times as defined by the i for loop above
# producers uptake rates
u1 = DATA1[i1,2] # in ecosystem 1
u2 = DATA1[i1,3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1,4]
a2 = DATA1[i1,5]
# death rates (= recycling rate)
h1 = DATA1[i1,6] # death (=recycling) rate for producers in ecosystems 1
h2 = DATA1[i1,7] # death (=recycling) rate for producers in ecosystem 2
d1 = DATA1[i1,8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1,9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1,10] # death rate in the disperser's pool Q
# efficiency of consumers
e1 = DATA2[i1,2] # in ecosystem 1
e2 = DATA2[i1,3] # in ecosystem 2
PREDtemp[i1,] <- c(i1, I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
((d1 - d1*e1 + g)*h1 + a1*e1*I1)/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(d1 + g)/(a1*e1),
(e1*(-h1*l + I1*u1))/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(-a1*e1*(d2*(-1 + e2)*h2 - a2*e2*I2)*l*(c + m) + d2*(-1 + e2)*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*(e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1))))/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)*(a2*e2*l - d2*(-1 + e2)*u2)),
(l*(-d2*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*e1*g*m*(-h1*l + I1*u1)) + d2*(d1*e1*I2*(c + m)*u1 - (d1 + g)*I2*(c + m)*u1 + e1*g*m*(h1*l - I1*u1))*u2 + a1*d2*e1*l*(c + m)*(h2*l - I2*u2))/(a2*e2*h2*l*(c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1) - a2*(a1*e1*e2*I2*l*(c + m) + e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1)))*u2),
((e1*g*m*(-h1*l + I1*u1)*u2)/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)) + e2*(-h2*l + I2*u2))/(a2*e2*l - d2*(-1 + e2)*u2))
}
PREDgvar <- rbind(PREDgvar, PREDtemp)
rm(PREDtemp)
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDgvar$Ntot <- PREDgvar$N1 + PREDgvar$N2
PREDgvar$Ptot <- PREDgvar$P1 + PREDgvar$P2
PREDgvar$Ctot <- PREDgvar$C1 + PREDgvar$C2
# Equilibrium stocks less than or equal to 0 make no biological sense, so we
# will remove them from the PRED dataset.
PREDgvarpos <- subset(PREDgvar, N1>0 & P1>0 & C1>0 & N2>0 & P2>0 & C2>0)
# PRED_sample100 <- PRED[sample(nrow(PRED),size=100),]
# pivot the
PREDgvar_long <- pivot_longer(PREDgvarpos, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", "Meta-ecosystem")))
PREDgvar_long$Compartment <- as_factor(PREDgvar_long$Compartment)
PREDgvar_long$Ecosystem <- as_factor(PREDgvar_long$Ecosystem)
comparts_medians <- PREDgvar_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data=PREDgvar_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.1, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 12: Biomass stock values at equilibrium for local and meta-ecosystem scale when m is held constant, g is allowed to vary, and the other parameters are randomly drawn. The consumers’ collapse in ecosystem 1, and consequent increase in producer biomass, is even more evident than in previous scenarios. Ecosystem 2 is largely untouched by this situation. Parameter values and ranges: m = 0, g = [0,10]. All other specifications as in Figure 1.
ggplot(data = PREDgvar_long, aes(x = g, y = log10(Stock), col = Ecosystem)) +
geom_point(alpha = 0.1) + geom_smooth(method = "lm", col = "black",
alpha = 1, se = T, lwd = 0.5) + facet_grid(. ~ Compartment) + scale_color_light() +
scale_fill_light() + theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 13: Change in biomass of the two ecosystems’ compartment, the dispersers’ pool, and the meta-ecostystem as g increases from 0 to 10, in increments of 0.5. THe value of m is held constant at 0. All other specifications as in Figure 2.
ggplot(PREDgvar_long, aes(c, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.1) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(. ~ Compartment) + scale_color_light() + scale_fill_light() +
theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 11))
Figure 14: Change in biomass of the two ecosystems’ compartment, the dispersers’ pool, and the meta-ecostystem with change in the value of the consumer death rate while in the dispersers’ pool (c) when g varies between 0 and 10. All specificatons as in Figure 2.
g and mInspecting the above results, it seems there may be an interaction between g and m, the two parameters that regulate movement to and from the Dispersers’ Pool (Q), respectively. Here, we try to disentangle this interactions, by randomly drawing m from a [0,10] distribution like before, but instead fixing the value of g. We will run three such simulations, with g = 0, g = 5, and g = 9.
gset <- c(0, 5, 9)
# Allocate an empty data frame to save the data in
PREDint <- NULL
for (j in 1:length(gset)) {
# movement rate to the Disperser's pool Q
g = gset[j]
PREDtemp <- NULL
PREDtemp <- rbind(PREDtemp, data.frame(TIME=seq(0,100,1), I1 = I1, I2 = I2, l = l, u1 = seq(0,100,1), u2 = seq(0,100,1), a1 = seq(0,100,1), a2 = seq(0,100,1), h1 = seq(0,100,1), h2 = seq(0,100,1), d1 = seq(0,100,1), d2 = seq(0,100,1), g = seq(0,100,1), m = seq(0,100,1), c = seq(0,100,1), e1 = seq(0,100,1), e2 = seq(0,100,1), N1=seq(0,100,1), P1=seq(0,100,1), C1=seq(0,100,1), N2=seq(0,100,1), P2=seq(0,100,1), C2=seq(0,100,1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0,9999,1)){
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x times as defined by the i for loop above
# producers uptake rates
u1 = DATA1[i1,2] # in ecosystem 1
u2 = DATA1[i1,3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1,4]
a2 = DATA1[i1,5]
# death rates (= recycling rate)
h1 = DATA1[i1,6] # death (=recycling) rate for producers in ecosystems 1
h2 = DATA1[i1,7] # death (=recycling) rate for producers in ecosystem 2
d1 = DATA1[i1,8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1,9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1,10] # death rate in the disperser's pool Q
# movement rate from the Disperser's pool Q
m = DATA1[i1,11]
# efficiency of consumers
e1 = DATA2[i1,2] # in ecosystem 1
e2 = DATA2[i1,3] # in ecosystem 2
PREDtemp[i1,] <- c(i1, I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
((d1 - d1*e1 + g)*h1 + a1*e1*I1)/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(d1 + g)/(a1*e1),
(e1*(-h1*l + I1*u1))/(a1*e1*l + (d1 - d1*e1 + g)*u1),
(-a1*e1*(d2*(-1 + e2)*h2 - a2*e2*I2)*l*(c + m) + d2*(-1 + e2)*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*(e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1))))/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)*(a2*e2*l - d2*(-1 + e2)*u2)),
(l*(-d2*(d1*(-1 + e1) - g)*h2*(c + m)*u1 + a2*e1*g*m*(-h1*l + I1*u1)) + d2*(d1*e1*I2*(c + m)*u1 - (d1 + g)*I2*(c + m)*u1 + e1*g*m*(h1*l - I1*u1))*u2 + a1*d2*e1*l*(c + m)*(h2*l - I2*u2))/(a2*e2*h2*l*(c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1) - a2*(a1*e1*e2*I2*l*(c + m) + e2*(d1 + g)*I2*(c + m)*u1 - e1*(d1*e2*I2*(c + m)*u1 + g*m*(h1*l - I1*u1)))*u2),
((e1*g*m*(-h1*l + I1*u1)*u2)/((c + m)*(a1*e1*l + (d1 - d1*e1 + g)*u1)) + e2*(-h2*l + I2*u2))/(a2*e2*l - d2*(-1 + e2)*u2))
}
PREDint<- rbind(PREDint, PREDtemp)
rm(PREDtemp)
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDint$Ntot <- PREDint$N1 + PREDint$N2
PREDint$Ptot <- PREDint$P1 + PREDint$P2
PREDint$Ctot <- PREDint$C1 + PREDint$C2
# Equilibrium stocks less than or equal to 0 make no biological sense, so we
# will remove them from the PRED dataset.
PREDintpos <- subset(PREDint, N1>0 & P1>0 & C1>0 & N2>0 & P2>0 & C2>0)
# PRED_sample100 <- PRED[sample(nrow(PRED),size=100),]
# pivot the
PREDint_long <- pivot_longer(PREDintpos, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", "Meta-ecosystem")))
PREDint_long$Compartment <- as_factor(PREDint_long$Compartment)
PREDint_long$Ecosystem <- as_factor(PREDint_long$Ecosystem)
PREDint_long$g <- as_factor(PREDint_long$g)
comparts_medians <- PREDint_long %>% dplyr::group_by(Compartment, Ecosystem, g) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
g_levels <- c("0" = "g = 0", "5" = "g = 5", "9" = "g = 9")
ggplot(data=PREDint_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.1, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(g~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(-5, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 15: Comparison of biomass stock values at equilibrium for local and meta-ecosystem scale for three cases of increasing g values; low (g = 0), medium (g = 5), and high (g = 9), respectively. All other parameters values, including m, are randomly drawn. In all scenarios, consumers collapse in ecosystem 1 following movement to the dispersers pool, leading to the establishment of a spatial trophic cascade at the meta-ecosystem scale. Simulations were ran for 1000 time steps for each value of g. All other specifications as in Figure 1.
ggplot(data = PREDint_long, aes(x = m, y = log10(Stock), fill = Ecosystem,
col = Ecosystem)) + geom_point(alpha = 0.1) + geom_smooth(method = "lm",
col = "black", alpha = 1, se = T, lwd = 0.5) + facet_grid(g ~ Compartment,
scales = "free", labeller = labeller(g = g_levels)) + scale_color_light() +
scale_fill_light() + theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 10))
Figure 16: Change in biomass stock values over change in the rate of consumer movement from the dispersers’ pool to ecosystem 2 (m), when the rate of consumer movememnt to the dispersers’ pool from ecosystem 1 (g) is drawn from three alternative values: low (g = 0), medium (g = 5), and high (g = 9). All other specifications as in Figure 2.
ggplot(PREDint_long, aes(c, log10(Stock), col = Ecosystem)) + geom_point(alpha = 0.15) +
geom_smooth(method = "lm", col = "black", alpha = 1, se = T, lwd = 0.5) +
facet_grid(g ~ Compartment, scale = "free", labeller = labeller(g = g_levels)) +
scale_color_light() + scale_fill_light() + theme_pubr() + guides(colour = guide_legend(override.aes = list(alpha = 1))) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, size = 8),
text = element_text(size = 10))
Figure 17: Change in biomass stock values with change in the value of the consumer death rate while in the dispersers’ pool (c), when g is either low (g = 0), medium (g = 5), and high (g = 9). All other specifications as in Figure 2.
Here, we investigate the influence of environmental fertility conditions on the productivity and nutrient flux at local and meta-ecosystem scales, when consumers move from ecosystem 1 to ecosystem 2. We will investigate two scenarios: higher environmental fertility in the donor ecosystem (i.e., I1 >> I2) and higher environmental fertility in the recipient ecosystem (i.e., I1 << I2). Note that the environmental leaching rate of each ecosystem (l)—i.e., the rate at which nutrient leave local ecosystems independently from trophic process—is invariant and equal between the two ecosystems.
The analysis will proceed along the same steps as before: draw parameter values from our DATA1 and DATA2 LHS-generate data objects, simulated model behaviour, run stability analyses, remove equilibria that are unstable and/or lack biological sense, sample a 1000 data point subset of the remaining iterations, graph the results.
Note that in the graphs below, we will not be showing values for the dispersers’ pool,
Q, as these tend to be negligible compared to the local and meta-ecosystem values for all ecosystem functions.
We begin by simulating a meta-ecosystem where the local donor ecosystem, ecosystem 1, has higher environmental fertility than the recipient ecosystem (ecosystem 2). Consumers move from ecosystem 1 to ecosystem 2 so that, in this case, our model is simulating diffusive organismal movement using a slightly different approach from previous studies (e.g., Gravel et al. 2010).
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 > I2
I1 = 18
I2 = 2
# Leaching does not change as is still equal across ecosystems
l = 0.1
# Allocate an empty data frame to save the data in
PREDI2 <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDI2 <- rbind(PREDI2, data.frame(TIME = seq(0, 100, 1), I1 = I1, I2 = I2,
l = l, u1 = seq(0, 100, 1), u2 = seq(0, 100, 1), a1 = seq(0, 100, 1),
a2 = seq(0, 100, 1), h1 = seq(0, 100, 1), h2 = seq(0, 100, 1), d1 = seq(0,
100, 1), d2 = seq(0, 100, 1), g = seq(0, 100, 1), m = seq(0, 100,
1), c = seq(0, 100, 1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1),
N1 = seq(0, 100, 1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0,
100, 1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100,
1), FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100, 1),
FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1), PROD_C2 = seq(0,
100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate)
h1 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystems 1
h2 = DATA1[i1, 7] # death (=recycling) rate for producers in ecosystem 2
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDI2[i1, ] <- c(i1, I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g,
m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 * e1 * I1)/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), (d1 + g)/(a1 * e1), (e1 * (-h1 *
l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (-a1 *
e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 *
(-1 + e2) * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 *
(e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 *
e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 +
e2) * u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 * e1 *
I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 + e1 * g *
m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 * l * (c + m) *
(h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l *
(c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 *
I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))) * u2), ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (e1 * g * (-h1 * l + I1 * u1))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) *
h1)/(a1 * e1), (d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) -
g) * h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 *
u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 *
(c + m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 * h2 *
l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), (d2 * ((e1 * g * m * (-h1 * l + I1 *
u1) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), ((d1 + g) * h1)/(a1 * e1) + (d1 * e1 * (-h1 * l + I1 *
u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (h2 * (l * (-d2 *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 *
e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 * g * m * (-h1 *
l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 *
(-1 + e2) * u2), (c * e1 * g * (-h1 * l + I1 * u1))/((c + m) *
(a1 * e1 * l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * ((d1 -
d1 * e1 + g) * h1 + a1 * e1 * I1) * u1)/(a1 * e1 * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), (e1 * (d1 + g) * (-h1 * l +
I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), ((-a1 *
e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 *
(-1 + e2) * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 *
(e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l -
d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l * (c + m) * (a1 *
e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 *
l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 *
e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))) * u2)),
(e2 * l * (-a1 * d2 * e1 * h2 * l * (c + m) + d2 * (d1 * (-1 +
e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m * (h1 * l -
I1 * u1)) + d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) * u2)))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDI2$Ntot <- PREDI2$N1 + PREDI2$N2
PREDI2$Ptot <- PREDI2$P1 + PREDI2$P2
PREDI2$Ctot <- PREDI2$C1 + PREDI2$C2
# now calculate recycling flux for the local ecosystem
PREDI2$FLUX_Eco1_check <- PREDI2$FLUX_P1 + PREDI2$FLUX_C1
PREDI2$FLUX_Eco2_check <- PREDI2$FLUX_P2 + PREDI2$FLUX_C2
# and the meta-ecosystem recycling flux
PREDI2$FLUX_Ptot <- PREDI2$FLUX_P1 + PREDI2$FLUX_P2
PREDI2$FLUX_Ctot <- PREDI2$FLUX_C1 + PREDI2$FLUX_C2
PREDI2$MetaEcoFlux <- PREDI2$FLUX_Eco_1 + PREDI2$FLUX_Eco_2 - PREDI2$FLUX_Q
# Finally, calculate the production for each compartment at the
# meta-ecosystem scale
PREDI2 <- dplyr::mutate(PREDI2, PROD_Ptot = PROD_P1 + PROD_P2, .after = "PROD_C2")
PREDI2 <- dplyr::mutate(PREDI2, PROD_Ctot = PROD_C1 + PROD_C2, .after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will flag them in the PREDI2 dataset.
PREDI2 <- PREDI2 %>% dplyr::mutate(., biosense = ifelse(N1 > 0 & P1 > 0 &
C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"), .after = MetaEcoFlux) %>%
dplyr::mutate_at(vars(biosense), factor)
Here we perform stability analyses for this model where I1 >> I2. The rationale and code are the same as above.
# create an empty dataframe
MathStabI2 <- NULL
for (i in 1:nrow(PREDI2)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDI2$I1[i]
I2 = PREDI2$I2[i]
l = PREDI2$l[i]
u1 = PREDI2$u1[i]
u2 = PREDI2$u2[i]
a1 = PREDI2$a1[i]
a2 = PREDI2$a2[i]
h1 = PREDI2$h1[i]
h2 = PREDI2$h2[i]
d1 = PREDI2$d1[i]
d2 = PREDI2$d2[i]
g = PREDI2$g[i]
m = PREDI2$m[i]
c = PREDI2$c[i]
e1 = PREDI2$e1[i]
e2 = PREDI2$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDI2$N1[i]
dP1 = PREDI2$P1[i]
dC1 = PREDI2$C1[i]
dN2 = PREDI2$N2[i]
dP2 = PREDI2$P2[i]
dC2 = PREDI2$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 * dC1 *
e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l - dP2 * u2,
h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 - h2 + dN2 *
u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 * dC2 * e2, -d2 +
a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStabI2 <- rbind(MathStabI2, data.frame(TIME = i, I1, I2, l, u1,
u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2, dN1, dP1, dC1, dN2,
dP2, dC2, EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEv = max(Re(base::eigen(Jacob)$values)), stable = stable, biosense = PREDI2$biosense[i]))
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
# browser()
}
MathStabI2$stable <- as.factor(MathStabI2$stable)
# separate unstable equilibria to work with later
MathStabI2US <- subset(MathStabI2, MathStabI2$stable == "unstable")
3.2% of the stability analyses runs produced unstable results (i.e., 324 out of 10000 iterations). Now we check if these are the same ones that produce equilibria where state variables are <0 at equilibrium (i.e., without biological plausibility).
I2biononsense <- subset(PREDI2, PREDI2$biosense == "no")
identical(as.numeric(MathStabI2US[, "TIME"]), I2biononsense[, "TIME"])
[1] TRUE
Since it appears that they are, let’s add the stable/unstable information to PREDI2.
Now, let’s exclude the unstable, biological nonsense parameter sets from the analyses below and from our results. Then, we will sample 1000 iterations out of PREDI2 to use in later figures.
PREDI2pos <- filter(PREDI2, PREDI2$biosense == "yes" & PREDI2$stable ==
"stable")
# sample PREDpos to only use 1000 random simulation results
PREDI2_sample1000 <- PREDI2pos[sample(nrow(PREDI2pos), size = 1000), ]
PREDI2_1k <- droplevels(PREDI2_sample1000)
# pivot the
PREDI2_biomass_long <- pivot_longer(PREDI2_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment =="Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::mutate(., Ecosystem = fct_relevel(Ecosystem, "Donor", "Recipient", "Meta-ecosystem")) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDI2_biomass_long$Compartment <- as_factor(PREDI2_biomass_long$Compartment)
PREDI2_biomass_long$Ecosystem <- as_factor(PREDI2_biomass_long$Ecosystem)
comparts_medians <- PREDI2_biomass_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
I2bm <- ggplot(data=PREDI2_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.25, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
ggtitle("Organic Biomass and Nutrients Stock") +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
I2bm
Figure 18: In the case of larger inorganic inputs into ecosystem 1 (i.e., I1 >> I2), consumers movement is diffusive. The magnitude of the producers’ release following consumers’ collapse in ecosystem 1 is larger than in the case when I1 = I2 (confront this figure with Figure 1). Environmental fertility values: I1 = 18, I2 = 2. All other specifications as in Figure 1.
FluxPREDI2 <- PREDI2_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDI2$Scale <- as.factor(FluxPREDI2$Scale)
FluxPREDI2$Compartment <- as.factor(FluxPREDI2$Compartment)
comparts_medians <- FluxPREDI2 %>% dplyr::group_by(Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
I2nf <- FluxPREDI2 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Scale, scales = "free") +
coord_cartesian(y = c(0, 300)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
ggtitle("Nutrient Flux") +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
I2nf
Figure 19: With a higher fertility in the ecosystem 1 (i.e., I1 >> I2) recycling flux in this ecosystem increases, mostly through increased biomass of primary producers. Conversely, ecosystem 2 shows lower and less variable levels of nutrient flux. At the meta-ecosystem scale, recycling flux is higher than in either local ecosystems but lower than in the case of equal environmental fertility across ecosystems (Figure 5). Environmental fertility values: I1 = 18, I2 = 2. All other specifications as in Figure 5.
# I2all <- I2bm + I2nf + plot_annotation(tag_levels = "a", tag_prefix = "(", tag_suffix = ")") + plot_layout(ncol = 1, nrow = 2)
#ggsave(I2all, filename = "../Results/LowI2BmNf.png", device = "png", dpi = 600, width = 8, height = 6)
Figure 20 below reports the results of a scenario in which environmental fertility in ecosystem 1 is smaller than that of ecosystem 2. In this case, consumer movement is non-diffusive, as it takes place against the resource availability (= fertility) gradient. The resulting spatial trophic cascade involving the local ecosystem is much stronger than in the scenario of equal fertility conditions (Figure 1).
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 < I2
I1 = 2
I2 = 18
# leaching rate
l = 0.1
# Allocate an empty data frame to save the data in
PREDI1 <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDI1 <- rbind(PREDI1, data.frame(TIME = seq(0, 100, 1), I1 = I1, I2 = I2,
l = l, u1 = seq(0, 100, 1), u2 = seq(0, 100, 1), a1 = seq(0, 100, 1),
a2 = seq(0, 100, 1), h1 = seq(0, 100, 1), h2 = seq(0, 100, 1), d1 = seq(0,
100, 1), d2 = seq(0, 100, 1), g = seq(0, 100, 1), m = seq(0, 100,
1), c = seq(0, 100, 1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1),
N1 = seq(0, 100, 1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0,
100, 1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100,
1), FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100, 1),
FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1), PROD_C2 = seq(0,
100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate)
h1 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystems 1
h2 = DATA1[i1, 7] # death (=recycling) rate for producers in ecosystem 2
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDI1[i1, ] <- c(i1, I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g,
m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 * e1 * I1)/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), (d1 + g)/(a1 * e1), (e1 * (-h1 *
l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (-a1 *
e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 *
(-1 + e2) * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 *
(e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 *
e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 +
e2) * u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 * e1 *
I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 + e1 * g *
m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 * l * (c + m) *
(h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l *
(c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 *
I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))) * u2), ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (e1 * g * (-h1 * l + I1 * u1))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) *
h1)/(a1 * e1), (d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) -
g) * h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 *
u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 *
(c + m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 * h2 *
l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), (d2 * ((e1 * g * m * (-h1 * l + I1 *
u1) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), ((d1 + g) * h1)/(a1 * e1) + (d1 * e1 * (-h1 * l + I1 *
u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (h2 * (l * (-d2 *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 *
e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 * g * m * (-h1 *
l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 *
(-1 + e2) * u2), (c * e1 * g * (-h1 * l + I1 * u1))/((c + m) *
(a1 * e1 * l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * ((d1 -
d1 * e1 + g) * h1 + a1 * e1 * I1) * u1)/(a1 * e1 * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), (e1 * (d1 + g) * (-h1 * l +
I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), ((-a1 *
e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 *
(-1 + e2) * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 *
(e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l -
d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l * (c + m) * (a1 *
e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 *
l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 *
e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))) * u2)),
(e2 * l * (-a1 * d2 * e1 * h2 * l * (c + m) + d2 * (d1 * (-1 +
e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m * (h1 * l -
I1 * u1)) + d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) * u2)))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDI1$Ntot <- PREDI1$N1 + PREDI1$N2
PREDI1$Ptot <- PREDI1$P1 + PREDI1$P2
PREDI1$Ctot <- PREDI1$C1 + PREDI1$C2
# now calculate recycling flux for the local ecosystem
PREDI1$FLUX_Eco1_check <- PREDI1$FLUX_P1 + PREDI1$FLUX_C1
PREDI1$FLUX_Eco2_check <- PREDI1$FLUX_P2 + PREDI1$FLUX_C2
# and the meta-ecosystem recycling flux
PREDI1$FLUX_Ptot <- PREDI1$FLUX_P1 + PREDI1$FLUX_P2
PREDI1$FLUX_Ctot <- PREDI1$FLUX_C1 + PREDI1$FLUX_C2
PREDI1$MetaEcoFlux <- PREDI1$FLUX_Eco_1 + PREDI1$FLUX_Eco_2 - PREDI1$FLUX_Q
# Finally, calculate the compartment production at meta-ecosystem scale
PREDI1 <- dplyr::mutate(PREDI1, PROD_Ptot = PROD_P1 + PROD_P2, .after = "PROD_C2")
PREDI1 <- dplyr::mutate(PREDI1, PROD_Ctot = PROD_C1 + PROD_C2, .after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PRED dataset.
PREDI1 <- PREDI1 %>% dplyr::mutate(., biosense = ifelse(N1 > 0 & P1 > 0 &
C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"), .after = MetaEcoFlux) %>%
dplyr::mutate_at(vars(biosense), factor)
Here we perform stability analyses for the scenario of higher environmental fertility in ecosystem 2, the recipient (i.e., I1 << I2). The analyses follow the established workflow (see above).
# create an empty dataframe
MathStabI1 <- NULL
for (i in 1:nrow(PREDI1)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDI1$I1[i]
I2 = PREDI1$I2[i]
l = PREDI1$l[i]
u1 = PREDI1$u1[i]
u2 = PREDI1$u2[i]
a1 = PREDI1$a1[i]
a2 = PREDI1$a2[i]
h1 = PREDI1$h1[i]
h2 = PREDI1$h2[i]
d1 = PREDI1$d1[i]
d2 = PREDI1$d2[i]
g = PREDI1$g[i]
m = PREDI1$m[i]
c = PREDI1$c[i]
e1 = PREDI1$e1[i]
e2 = PREDI1$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDI1$N1[i]
dP1 = PREDI1$P1[i]
dC1 = PREDI1$C1[i]
dN2 = PREDI1$N2[i]
dP2 = PREDI1$P2[i]
dC2 = PREDI1$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 * dC1 *
e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l - dP2 * u2,
h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 - h2 + dN2 *
u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 * dC2 * e2, -d2 +
a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStabI1 <- rbind(MathStabI1, data.frame(TIME = i, I1, I2, l, u1,
u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2, dN1, dP1, dC1, dN2,
dP2, dC2, EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEv = max(Re(base::eigen(Jacob)$values)), stable = stable, biosense = PREDI1$biosense[i]))
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
# browser()
}
MathStabI1$stable <- as.factor(MathStabI1$stable)
# separate unstable equilibria to work with later
MathStabI1US <- subset(MathStabI1, MathStabI1$stable == "unstable")
2.9% of the stability analyses runs produced unstable results (i.e., 285 out of 10000 iterations). As always, let’s check whether these are also the parameter sets that produce state variable values at equilibrium that do not have biological meaning (i.e., <0).
I1biononsense <- subset(PREDI1, PREDI1$biosense == "no")
identical(as.numeric(MathStabI1US[, "TIME"]), I1biononsense[, "TIME"])
[1] TRUE
Now, since that proved to be the case, let’s add the stable/unstable information to PREDI1.
Now, let’s exclude the unstable, biological nonsense parameter sets from the analyses below and from our results. Then, let’s sample 1000 random iterations from PREDI1 and store them for later use in figures and comparisons.
PREDI1pos <- filter(PREDI1, PREDI1$biosense == "yes" & PREDI1$stable ==
"stable")
# sample PREDpos to only use 1000 random simulation results
PREDI1_sample1000 <- PREDI1pos[sample(nrow(PREDI1pos), size = 1000), ]
PREDI1_1k <- droplevels(PREDI1_sample1000)
# pivot the
PREDI1_biomass_long <- pivot_longer(PREDI1_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::mutate(., Ecosystem = fct_relevel(Ecosystem, "Donor", "Recipient", "Meta-ecosystem")) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDI1_biomass_long$Compartment <- as_factor(PREDI1_biomass_long$Compartment)
PREDI1_biomass_long$Ecosystem <- as_factor(PREDI1_biomass_long$Ecosystem)
comparts_medians <- PREDI1_biomass_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
I1bm <- ggplot(data=PREDI1_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.25, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
ggtitle("Organic Biomass and Nutrient Stock") +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
I1bm
Figure 20: Movement of consumer against the environmental fertility gradient leads to a strong reduction of the nutrients stock in ecosystem 1—i.e., the source of consumer on the move—stemming from the release of local primary producers from consumer control. In ecosystem 2, the recipient of consumers movement and more fertility ecosystem, the nutrients stock increases beyond the level of enrichment seen in Figure 1, as local and immigrant consumer strongly suppress the local primary producers. All other specifications as in Figure 1. Environmental fertility values: I1 = 2, I2 = 18.
Following from the stronger trophic cascades, nutrient flux is also affected at both local and meta-ecosystem scales when consumer movement happens against the local fertility gradient. Here, median nutrient flux in the recipient, and more fertile, ecosystem 2 is more than double the median flux in the case of diffusive consumer movement (Figure 19). Furthermore, it is also higher than in the case of equal local environmental fertility—i.e., lack of a fertility gradient (Figure 5). Likewise, median nutrient flux at the meta-ecosystem scale is higher than in either the along-gradient (Figure 19) and no-gradient (Figure 5) cases.
FluxPREDI1 <- PREDI1_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDI1$Scale <- as.factor(FluxPREDI1$Scale)
FluxPREDI1$Compartment <- as.factor(FluxPREDI1$Compartment)
comparts_medians <- FluxPREDI1 %>% dplyr::group_by(Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
I1nf <- FluxPREDI1 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(0, 400)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
ggtitle("Nutrient Flux") +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
I1nf
Figure 21: Nutrient flux more than doubles in ecosystem 2, when this is the more fertile of the two ecosystem and is also the recipient of consumer movement. In turn, this leads to an overall increase in the nutrient flux at the meta-ecosystem scale, compared to scenarios where environmental fertility is equal among local ecosystem (Figure 5) or is higher in the donor ecosystem (Figure 19). Environmental fertility values: I1 = 2, I2 = 18.
# I1all <- I1bm + I1nf + plot_annotation(tag_levels = "a", tag_prefix = "(", tag_suffix = ")") + plot_layout(ncol = 1, nrow = 2)
#ggsave(I1all, filename = "../Results/LowI1BmNf.png", device = "png", dpi = 600, width = 8, height = 6)
How does non-diffusive organismal movement interact with other local and meta-ecosystem processes? We have shown above that non-diffusive, against-gradient consumer movement can influence local and meta-ecosystem productivity and lead to the establishment spatial trophic cascades (Figures 1, 18, 20; Knight et al. (2005), Massol et al. (2017)). Indeed, nutrient flux at both local and meta-ecosystem scales reflects this (Figures 5, 19, 21). However, multiple parameters influence ecosystem processes like productivity and nutrient flux, both in our model and in the real world.
Here we focus on primary producers recycling rate and investigate how the effects of non-diffusive consumer movement on primary productivity and nutrient flux change when we consider a meta-ecosystem in which primary producers in the local ecosystem differ in their recycling rates. As we are modeling unidirectional non-diffusive consumer movement, we will consider three cases:
We will evaluate these three cases in each one of the three environmental fertility scenarios simulated above—i.e., equal, higher in donor, and higher in recipient fertility.
We begin by simulating model behavior when recycling rates of primary producers in the two local ecosystems are equal—i.e., h1 = h2—in equal environmental fertility conditions. This will help set a baseline for other combinations of environmental fertility and producers recycling rates scenarios. The code, in this case, is much the same as in previous simulations, with the important difference that—for each simulation—we are drawing the values for h1 and h2 from the same column of dataframe DATA1.
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 = I2
I1 = 10
I2 = 10
# leaching rate
l = 0.1
# Allocate an empty data frame to save the data in
PREDeqrecrates <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDeqrecrates <- rbind(PREDeqrecrates, data.frame(TIME = seq(0, 100, 1),
hDIFF = seq(0, 100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1),
u2 = seq(0, 100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100, 1),
FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1), PROD_C2 = seq(0,
100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---in this scenario these
# are equal
h1 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 1---NOTE: it is equal to h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2---NOTE: it is equal to h1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDeqrecrates[i1, ] <- c(i1, (h1 - h2), I1, I2, l, u1, u2, a1, a2,
h1, h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 + g)/(a1 *
e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (-a1 * e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) *
l * (c + m) + d2 * (-1 + e2) * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * (e2 * (d1 + g) * I2 * (c + m) * u1 - e1 *
(d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l -
d2 * (-1 + e2) * u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 *
e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 -
e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) * u2)/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 *
l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) * u2), (e1 * g *
(-h1 * l + I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)), ((d1 + g) * h1)/(a1 * e1), (d1 * e1 * (-h1 * l +
I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (h2 * (l *
(-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 *
g * m * (-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c +
m) * u1 - (d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 *
l - I1 * u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l -
I2 * u2)))/(a2 * e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) +
e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 *
e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 -
e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2) + (d2 * ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) + e2 *
(-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 * (-1 + e2) * u2),
(c * e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 +
a1 * e1 * I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 * (-1 +
e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1)))) * u2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2)))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2) * (a2 * e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 *
e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 *
(d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 *
d2 * e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) + d2 *
e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 * (c + m) *
u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) * (a2 * e2 * l - d2 * (-1 + e2) * u2)))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDeqrecrates$Ntot <- PREDeqrecrates$N1 + PREDeqrecrates$N2
PREDeqrecrates$Ptot <- PREDeqrecrates$P1 + PREDeqrecrates$P2
PREDeqrecrates$Ctot <- PREDeqrecrates$C1 + PREDeqrecrates$C2
# now calculate recycling flux for the local ecosystem
PREDeqrecrates$FLUX_Eco1_check <- PREDeqrecrates$FLUX_P1 + PREDeqrecrates$FLUX_C1
PREDeqrecrates$FLUX_Eco2_check <- PREDeqrecrates$FLUX_P2 + PREDeqrecrates$FLUX_C2
# and the meta-ecosystem recycling flux
PREDeqrecrates$FLUX_Ptot <- PREDeqrecrates$FLUX_P1 + PREDeqrecrates$FLUX_P2
PREDeqrecrates$FLUX_Ctot <- PREDeqrecrates$FLUX_C1 + PREDeqrecrates$FLUX_C2
PREDeqrecrates$MetaEcoFlux <- PREDeqrecrates$FLUX_Eco_1 + PREDeqrecrates$FLUX_Eco_2 -
PREDeqrecrates$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDeqrecrates <- dplyr::mutate(PREDeqrecrates, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDeqrecrates <- dplyr::mutate(PREDeqrecrates, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDeqrecrates dataset.
PREDeqrecrates <- PREDeqrecrates %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
Here, we run stability analyses for the scenario of equal environmental fertility (I1 = I2) and equal primary producers recycling rates (h1 = h2) simulated above. Let’s solve the model’s Jacobian numerically.
# create an empty dataframe
MathStab_eqIeqh <- NULL
for (i in 1:nrow(PREDeqrecrates)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDeqrecrates$I1[i]
I2 = PREDeqrecrates$I2[i]
l = PREDeqrecrates$l[i]
u1 = PREDeqrecrates$u1[i]
u2 = PREDeqrecrates$u2[i]
a1 = PREDeqrecrates$a1[i]
a2 = PREDeqrecrates$a2[i]
h1 = PREDeqrecrates$h1[i]
h2 = PREDeqrecrates$h2[i]
d1 = PREDeqrecrates$d1[i]
d2 = PREDeqrecrates$d2[i]
g = PREDeqrecrates$g[i]
m = PREDeqrecrates$m[i]
c = PREDeqrecrates$c[i]
e1 = PREDeqrecrates$e1[i]
e2 = PREDeqrecrates$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDeqrecrates$N1[i]
dP1 = PREDeqrecrates$P1[i]
dC1 = PREDeqrecrates$C1[i]
dN2 = PREDeqrecrates$N2[i]
dP2 = PREDeqrecrates$P2[i]
dC2 = PREDeqrecrates$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 * dC1 *
e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l - dP2 * u2,
h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 - h2 + dN2 *
u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 * dC2 * e2, -d2 +
a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_eqIeqh <- rbind(MathStab_eqIeqh, data.frame(TIME = i, I1,
I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2, dN1, dP1,
dC1, dN2, dP2, dC2, EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEv = max(Re(base::eigen(Jacob)$values)), stable = stable, biosense = PREDeqrecrates$biosense[i]))
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
# browser()
}
MathStab_eqIeqh$stable <- as.factor(MathStab_eqIeqh$stable)
# separate unstable equilibria to work with later
MathStab_eqIeqhUS <- subset(MathStab_eqIeqh, MathStab_eqIeqh$stable ==
"unstable")
1.5% of the stability analyses runs produced unstable results (i.e., 153 out of 10000). Let’s see if these are these also the same as those that return equilibria without biological sense (i.e., state variables values at equilibrium <0).
biononsense_eqIeqh <- subset(PREDeqrecrates, PREDeqrecrates$biosense ==
"no")
identical(as.numeric(MathStab_eqIeqhUS[, "TIME"]), biononsense_eqIeqh[,
"TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDeqrecrates.
Now, let’s exclude the unstable, biological nonsense parameter sets from the analyses below and from our results, before sampling 1000 random iterations to use in later figures and comparisons.
PREDeqIeqh_pos <- filter(PREDeqrecrates, PREDeqrecrates$biosense == "yes" &
PREDeqrecrates$stable == "stable")
# sample PREDpos to only use 1000 random simulation results
PREDeqIeqh_sample1000 <- PREDeqIeqh_pos[sample(nrow(PREDeqIeqh_pos), size = 1000),
]
PREDeqIeqh_1k <- droplevels(PREDeqIeqh_sample1000)
The graph below shows the variation in nutrient stock and median biomass values for all trophic compartments at local and meta-ecosystem scales.
# pivot the data frame containing the predictions
PREDeqrecrates_biomass_long <- pivot_longer(PREDeqIeqh_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDeqrecrates_biomass_long$Compartment <- as_factor(PREDeqrecrates_biomass_long$Compartment)
PREDeqrecrates_biomass_long$Ecosystem <- as_factor(PREDeqrecrates_biomass_long$Ecosystem)
comparts_medians <- PREDeqrecrates_biomass_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
eqIbm <- ggplot(data=PREDeqrecrates_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.25, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
ggtitle("Organism Biomass and Nutrient Stock") +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
eqIbm
Figure 22: Nutrient stock and biomass values at local and meta-ecosystem scales when h1 = h2, and environmental fertility is the same in both local ecosystems. The median values are virtually indistinguishable from those in Figure 1.
Nutrient flux in both local and meta-ecosystem when h1 = h2 (Figure 23) does not appear to differ from the above simulations in which h1 and h2 were both randomly drawn from a uniform distribution scaled between [0,10] (Figure 5).
FluxPREDeqrecrates <- PREDeqIeqh_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDeqrecrates$Scale <- as.factor(FluxPREDeqrecrates$Scale)
FluxPREDeqrecrates$Compartment <- as.factor(FluxPREDeqrecrates$Compartment)
comparts_medians <- FluxPREDeqrecrates %>% dplyr::group_by(Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
eqInf <- FluxPREDeqrecrates %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(0, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
ggtitle("Nutrient Flux") +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
eqInf
Figure 23: Nutrient flux median values in all trophic compartment at the two scales of interest, local and meta-ecosystem. There is a small increase in the values of nutrient flux for producers and consumers, in the recipient ecosystem, and for the whole meta-ecosystem. Curiously, while individual trophic compartments in ecosystem 2 show higher flux values comapred to Figure 5, nutrient flux for the whole recipient ecosystem is slightly lower than in Figure 5.
# eqIall <- eqIbm + eqInf + plot_annotation(tag_levels = "a", tag_prefix = "(", tag_suffix = ")") + plot_layout(ncol = 1, nrow = 2)
#ggsave(eqIall, filename = "../Results/BM_eqIeqh.png", device = "png", dpi = 600, width = 8, height = 6)
To investigate the effects of higher recycling rate in the donor ecosystem, we will simulate the model’s behavior using differences of increasing magnitude. We will start with a 10% difference in the producers’ recycling rate in donor ecosystem compared to the recipient ecosystem and then move on from there to test larger differences. The mathematical formula that we will use to apply the increase is \(h_1 = h_2 + (h_2 \cdot x)\), where \(x\) varies from 10% up to 90% by 20% increments.
First, we store the increasing \(x\) values in a new vector, hdiff.
hdiff <- seq(0.1, 0.9, 0.2)
Then, we again use a nested loop to run the simulations with each element in hdiff.
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 = I2
I1 = 10
I2 = 10
# leaching rate
l = 0.1
# allocate empty dataframe to store simulations results
PREDhigh_h1 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtemp <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDtemp <- rbind(PREDtemp, data.frame(TIME = seq(0, 100, 1), hDIFF = seq(0,
100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1), u2 = seq(0,
100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100,
1), FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1),
PROD_C2 = seq(0, 100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---h1 > h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2
h1 = h2 + (h2 * hdiff[j]) # death (=recycling) rate for producers in ecosystem 1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDtemp[i1, ] <- c(i1, hdiff[j], I1, I2, l, u1, u2, a1, a2, h1,
h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 +
g)/(a1 * e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 +
g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 *
e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 *
e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), (e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * h1)/(a1 * e1),
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 * g *
m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 *
e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) *
h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (c * e1 * g * (-h1 * l +
I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 + a1 * e1 *
I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 +
e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 *
u2)))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) *
(a2 * e2 * l - d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l *
(c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 * d2 *
e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) +
d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)))
}
# store the results from the j-th round of simulations into the
# persistent dataframe
PREDhigh_h1 <- rbind(PREDhigh_h1, PREDtemp)
# print the percentage used in this round of simulations #
# print(hdiff[j])
# remove the temporary dataframe
rm(PREDtemp)
# browser()
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDhigh_h1$Ntot <- PREDhigh_h1$N1 + PREDhigh_h1$N2
PREDhigh_h1$Ptot <- PREDhigh_h1$P1 + PREDhigh_h1$P2
PREDhigh_h1$Ctot <- PREDhigh_h1$C1 + PREDhigh_h1$C2
# now calculate recycling flux for the local ecosystem
PREDhigh_h1$FLUX_Eco1_check <- PREDhigh_h1$FLUX_P1 + PREDhigh_h1$FLUX_C1
PREDhigh_h1$FLUX_Eco2_check <- PREDhigh_h1$FLUX_P2 + PREDhigh_h1$FLUX_C2
# and the meta-ecosystem recycling flux
PREDhigh_h1$FLUX_Ptot <- PREDhigh_h1$FLUX_P1 + PREDhigh_h1$FLUX_P2
PREDhigh_h1$FLUX_Ctot <- PREDhigh_h1$FLUX_C1 + PREDhigh_h1$FLUX_C2
PREDhigh_h1$MetaEcoFlux <- PREDhigh_h1$FLUX_Eco_1 + PREDhigh_h1$FLUX_Eco_2 -
PREDhigh_h1$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDhigh_h1 <- dplyr::mutate(PREDhigh_h1, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDhigh_h1 <- dplyr::mutate(PREDhigh_h1, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDhigh_h1 dataset.
PREDhigh_h1 <- PREDhigh_h1 %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
As in earlier scenarios, here we run stability analyses for this new batch of model simulations. Note that, in this case, we use a nested for loop to run stability analyses independently for each value of \(x \in [0.1, 0.9]\) contained in object hdiff. Index j will let us move through the elements of hdiff, whereas index i will move us through each parameter set. For each j, we will first subset PREDhigh_h1 to include only the corresponding scenario of differences between h1 and h2, store these values in a temporary dataframe (PREDtempSA), and then run the stability analyses on this temporary object.
# We will store all the stability analyses for the various levels of
# hdiff here
MathStab_h1 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtempSA <- subset(PREDhigh_h1, PREDhigh_h1$hDIFF == hdiff[j])
MathStab_temp <- NULL
MathStab_temp <- rbind(MathStab_temp, data.frame(TIME = seq(0, 100,
1), hDIFF = seq(0, 100, 1), I1 = seq(0, 100, 1), I2 = seq(0, 100,
1), l = seq(0, 100, 1), u1 = seq(0, 100, 1), u2 = seq(0, 100, 1),
a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0, 100, 1),
h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0, 100, 1),
g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100, 1), e1 = seq(0,
100, 1), e2 = seq(0, 100, 1), EiV1 = seq(0, 100, 1), EiV2 = seq(0,
100, 1), EiV3 = seq(0, 100, 1), EiV4 = seq(0, 100, 1), EiV5 = seq(0,
100, 1), EiV6 = seq(0, 100, 1), maxEV = seq(0, 100, 1), biosense = NA,
stable = NA))
for (i in 1:nrow(PREDtempSA)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDtempSA$I1[i]
I2 = PREDtempSA$I2[i]
l = PREDtempSA$l[i]
u1 = PREDtempSA$u1[i]
u2 = PREDtempSA$u2[i]
a1 = PREDtempSA$a1[i]
a2 = PREDtempSA$a2[i]
h1 = PREDtempSA$h1[i]
h2 = PREDtempSA$h2[i]
d1 = PREDtempSA$d1[i]
d2 = PREDtempSA$d2[i]
g = PREDtempSA$g[i]
m = PREDtempSA$m[i]
c = PREDtempSA$c[i]
e1 = PREDtempSA$e1[i]
e2 = PREDtempSA$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDtempSA$N1[i]
dP1 = PREDtempSA$P1[i]
dC1 = PREDtempSA$C1[i]
dN2 = PREDtempSA$N2[i]
dP2 = PREDtempSA$P2[i]
dC2 = PREDtempSA$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 *
dC1 * e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l -
dP2 * u2, h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 -
h2 + dN2 * u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 *
dC2 * e2, -d2 + a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_temp[i, ] <- c(TIME = i, hDIFF = PREDtempSA$hDIFF[i],
I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEV = max(Re(base::eigen(Jacob)$values)), biosense = as.character(PREDtempSA$biosense[i]),
stable = stable)
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
}
# now store everything in the dataframe created earlier, for ease of
# reference
MathStab_h1 <- rbind(MathStab_h1, MathStab_temp)
# browser()
# remove the temporary dataframe to avoid mistakes
rm(MathStab_temp)
}
# remove PREDtempSA to avoid mistakes in later use of similarly named
# objects
rm(PREDtempSA)
MathStab_h1[, c(1:25)] <- lapply(MathStab_h1[, c(1:25)], as.numeric)
MathStab_h1[, 26:27] <- lapply(MathStab_h1[, 26:27], as.factor)
# separate unstable equilibria to work with later
MathStab_h1US <- subset(MathStab_h1, MathStab_h1$stable == "unstable")
In this scenario, 1.8% of the stability analyses runs produced unstable results (i.e., 878 out of 50000 iterations). Let’s check if these are also the equilibria with no biological sense (i.e., state variable values <0).
biononsense_h1 <- subset(PREDhigh_h1, PREDhigh_h1$biosense == "no")
identical(as.numeric(MathStab_h1US[, "TIME"]), biononsense_h1[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDhigh_h1. First, we need to edit column TIME in PREDhigh_h1 and MathStab_h1 to be real indexing column. By this, we mean that it currently has repeated entries because it was populated with i1 in the loop above. We are going to copy the rownames of the dataframes to TIME, thus turning it in a real indexing variable.
PREDhigh_h1$TIME <- as.numeric(rownames(PREDhigh_h1))
MathStab_h1$TIME <- as.numeric(rownames(MathStab_h1))
Now, let’s copy the stability information into PREDhigh_h1 and then exclude the unstable, biological nonsense parameter sets that produce these equilibria that are both unstable and devoid of biological sense from the analyses below and from our results.
We will sample 1000 iterations from the remaining ones to use in later figures and comparisons. Note that we sample 1000 iterations for each successive value of hDIFF, so the final PREDhigh_h1_1k object will contains 5000 rows.
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
PREDhigh_h1_1k <- droplevels(PREDhigh_h1_1k)
# remove to avoid mistakes in later iterations of this sampling process
rm(temp, temp1000)
# pivot the data frame containing the predictions
PREDhigh_h1_biomass_long <- pivot_longer(PREDhigh_h1_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDhigh_h1_biomass_long$Compartment <- as_factor(PREDhigh_h1_biomass_long$Compartment)
PREDhigh_h1_biomass_long$Ecosystem <- as_factor(PREDhigh_h1_biomass_long$Ecosystem)
PREDhigh_h1_biomass_long$hDIFF <- as_factor(PREDhigh_h1_biomass_long$hDIFF)
comparts_medians <- PREDhigh_h1_biomass_long %>% dplyr::group_by(hDIFF, Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data = PREDhigh_h1_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
ggtitle("Organism Biomass and Nutrient Stock") +
theme_pubr() +
coord_cartesian(y = c(-15, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 24: At higher producers recycling rates in the donor ecosystem, compared to the recipient ecosystems, correspond higher median nutrient stocks in the donor ecosystem. Grey labels above the graphs show the scale of investigation (local, meta-ecosystems). Grey labels on the right-hand side of the graphs indicate the increase in h1 compared to h2 (\(x\)). All specifications as in Figure 1.
FluxPREDhigh_h1 <- PREDhigh_h1_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDhigh_h1$Scale <- as.factor(FluxPREDhigh_h1$Scale)
FluxPREDhigh_h1$Compartment <- as.factor(FluxPREDhigh_h1$Compartment)
FluxPREDhigh_h1$hDIFF <- as.factor(FluxPREDhigh_h1$hDIFF)
comparts_medians <- FluxPREDhigh_h1 %>% dplyr::group_by(hDIFF, Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDhigh_h1 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Scale, scales = "free") +
ggtitle("Nutrient Flux") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(-20, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Figure 25: As for biomass and stocks, differences in producers recycling rates between local ecosystem influence nutrient flux at both local and meta-ecosystem scales. Here the increase in the median value for nutrient flux in both producers trophic compartment and the donor ecosystem (Ecosystem 1) overall is clearly visible, as it is at the meta-ecosystem scale.
Here, we reverse the scenario above, investigating how primary productivity and nutrient flux vary at local and meta-ecosystem scales when the producers’ recycling rate in the donor ecosystem is lower than in the recipient ecosystem. As above, we will use a set of five threshold differences in these simulations (object hdiff).
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 = I2
I1 = 10
I2 = 10
# leaching rate
l = 0.1
# allocate empty dataframe to store simulations results
PREDhigh_h2 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtemp <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDtemp <- rbind(PREDtemp, data.frame(TIME = seq(0, 100, 1), hDIFF = seq(0,
100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1), u2 = seq(0,
100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100,
1), FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1),
PROD_C2 = seq(0, 100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---h1 < h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2
h1 = h2 - (h2 * hdiff[j]) # death (=recycling) rate for producers in ecosystem 1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDtemp[i1, ] <- c(i1, hdiff[j], I1, I2, l, u1, u2, a1, a2, h1,
h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 +
g)/(a1 * e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 +
g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 *
e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 *
e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), (e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * h1)/(a1 * e1),
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 * g *
m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 *
e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) *
h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (c * e1 * g * (-h1 * l +
I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 + a1 * e1 *
I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 +
e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 *
u2)))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) *
(a2 * e2 * l - d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l *
(c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 * d2 *
e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) +
d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)))
}
# store the results from the j-th round of simulations into the
# persistent dataframe
PREDhigh_h2 <- rbind(PREDhigh_h2, PREDtemp)
# print the percentage used in this round of simulations
# print(hdiff[j])
# remove the temporary dataframe
rm(PREDtemp)
# browser()
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDhigh_h2$Ntot <- PREDhigh_h2$N1 + PREDhigh_h2$N2
PREDhigh_h2$Ptot <- PREDhigh_h2$P1 + PREDhigh_h2$P2
PREDhigh_h2$Ctot <- PREDhigh_h2$C1 + PREDhigh_h2$C2
# now calculate recycling flux for the local ecosystem
PREDhigh_h2$FLUX_Eco1_check <- PREDhigh_h2$FLUX_P1 + PREDhigh_h2$FLUX_C1
PREDhigh_h2$FLUX_Eco2_check <- PREDhigh_h2$FLUX_P2 + PREDhigh_h2$FLUX_C2
# and the meta-ecosystem recycling flux
PREDhigh_h2$FLUX_Ptot <- PREDhigh_h2$FLUX_P1 + PREDhigh_h2$FLUX_P2
PREDhigh_h2$FLUX_Ctot <- PREDhigh_h2$FLUX_C1 + PREDhigh_h2$FLUX_C2
PREDhigh_h2$MetaEcoFlux <- PREDhigh_h2$FLUX_Eco_1 + PREDhigh_h2$FLUX_Eco_2 -
PREDhigh_h2$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDhigh_h2 <- dplyr::mutate(PREDhigh_h2, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDhigh_h2 <- dplyr::mutate(PREDhigh_h2, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDhigh_h2 dataset.
PREDhigh_h2 <- PREDhigh_h2 %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
As in earlier scenarios, here we run stability analyses for this new batch of model simulations. We follow the same process used for stability analyses for the Higher producers’ recycling rate in the donor ecosystem scenario.
# We will store all the stability analyses for the various levels of
# hdiff here
MathStab_h2 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtempSA <- subset(PREDhigh_h2, PREDhigh_h2$hDIFF == hdiff[j])
MathStab_temp <- NULL
MathStab_temp <- rbind(MathStab_temp, data.frame(TIME = seq(0, 100,
1), hDIFF = seq(0, 100, 1), I1 = seq(0, 100, 1), I2 = seq(0, 100,
1), l = seq(0, 100, 1), u1 = seq(0, 100, 1), u2 = seq(0, 100, 1),
a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0, 100, 1),
h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0, 100, 1),
g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100, 1), e1 = seq(0,
100, 1), e2 = seq(0, 100, 1), EiV1 = seq(0, 100, 1), EiV2 = seq(0,
100, 1), EiV3 = seq(0, 100, 1), EiV4 = seq(0, 100, 1), EiV5 = seq(0,
100, 1), EiV6 = seq(0, 100, 1), maxEV = seq(0, 100, 1), biosense = NA,
stable = NA))
for (i in 1:nrow(PREDtempSA)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDtempSA$I1[i]
I2 = PREDtempSA$I2[i]
l = PREDtempSA$l[i]
u1 = PREDtempSA$u1[i]
u2 = PREDtempSA$u2[i]
a1 = PREDtempSA$a1[i]
a2 = PREDtempSA$a2[i]
h1 = PREDtempSA$h1[i]
h2 = PREDtempSA$h2[i]
d1 = PREDtempSA$d1[i]
d2 = PREDtempSA$d2[i]
g = PREDtempSA$g[i]
m = PREDtempSA$m[i]
c = PREDtempSA$c[i]
e1 = PREDtempSA$e1[i]
e2 = PREDtempSA$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDtempSA$N1[i]
dP1 = PREDtempSA$P1[i]
dC1 = PREDtempSA$C1[i]
dN2 = PREDtempSA$N2[i]
dP2 = PREDtempSA$P2[i]
dC2 = PREDtempSA$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 *
dC1 * e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l -
dP2 * u2, h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 -
h2 + dN2 * u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 *
dC2 * e2, -d2 + a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_temp[i, ] <- c(TIME = i, hDIFF = PREDtempSA$hDIFF[i],
I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEV = max(Re(base::eigen(Jacob)$values)), biosense = as.character(PREDtempSA$biosense[i]),
stable = stable)
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
}
# now store everything in the dataframe created earlier, for ease of
# reference
MathStab_h2 <- rbind(MathStab_h2, MathStab_temp)
# browser()
# remove the temporary dataframe to avoid mistakes
rm(MathStab_temp)
}
# remove PREDtempSA to avoid mistakes in later use of similarly named
# objects
rm(PREDtempSA)
MathStab_h2[, c(1:25)] <- lapply(MathStab_h2[, c(1:25)], as.numeric)
MathStab_h2[, 26:27] <- lapply(MathStab_h2[, 26:27], as.factor)
# separate unstable equilibria to work with later
MathStab_h2US <- subset(MathStab_h2, MathStab_h2$stable == "unstable")
In this scenario, 1.3% of the stability analyses runs produced unstable results (i.e., 629 out of 50000 iterations). Let’s check if these are also the equilibria with no biological sense (i.e., state variable values <0).
biononsense_h2 <- subset(PREDhigh_h2, PREDhigh_h2$biosense == "no")
identical(as.numeric(MathStab_h2US[, "TIME"]), biononsense_h2[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDhigh_h2. First, we need to edit column TIME in PREDhigh_h2 and MathStab_h2 to be real indexing column. By this, we mean that it currently has repeated entries because it was populated with i1 in the loop above. We are going to copy the rownames of the dataframes to TIME, thus turning it in a real indexing variable.
PREDhigh_h2$TIME <- as.numeric(rownames(PREDhigh_h2))
MathStab_h2$TIME <- as.numeric(rownames(MathStab_h2))
Now, let’s copy the stability information into PREDhigh_h2 and then exclude the unstable, biological nonsense parameter sets that produce these equilibria that are both unstable and devoid of biological sense from the analyses below and from our results. We then sample 1000 random iterations from the PREDhigh_h2 dataset to use when creating figures and comparing with other scenarios.
We will sample 1000 random iterations to include in later figures and comparisons.
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
PREDhigh_h2_1k <- droplevels(PREDhigh_h2_1k)
rm(temp, temp1000)
# pivot the data frame containing the predictions
PREDhigh_h2_biomass_long <- pivot_longer(PREDhigh_h2_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDhigh_h2_biomass_long$Compartment <- as_factor(PREDhigh_h2_biomass_long$Compartment)
PREDhigh_h2_biomass_long$Ecosystem <- as_factor(PREDhigh_h2_biomass_long$Ecosystem)
PREDhigh_h2_biomass_long$hDIFF <- as_factor(PREDhigh_h2_biomass_long$hDIFF)
comparts_medians <- PREDhigh_h2_biomass_long %>% dplyr::group_by(hDIFF, Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data = PREDhigh_h2_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(-15, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 26: Nutrients stocks and biomass values when h1 is increasingly smaller than h2. The reduction in producers recycling rates in the donor ecosystem, compared to the recipient, leads to a strong decrease of the median value of nutrients stock in this patch. In turn, this leads to a lower nutrient availability at the meta-ecosystem scale.
FluxPREDhigh_h2 <- PREDhigh_h2_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDhigh_h2$Scale <- as.factor(FluxPREDhigh_h2$Scale)
FluxPREDhigh_h2$Compartment <- as.factor(FluxPREDhigh_h2$Compartment)
FluxPREDhigh_h2$hDIFF <- as.factor(FluxPREDhigh_h2$hDIFF)
comparts_medians <- FluxPREDhigh_h2 %>% dplyr::group_by(hDIFF, Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDhigh_h2 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines")) +
facet_grid(hDIFF~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(-35, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Figure 27: Median nutrient flux falls in the donor ecosystem (ecosystem 1) as a consequence of the lower producers’ recycling rate in it, compared to ecosystem 2 (the recipient). In turn, this leads to an overall reduction of nutrient flux when considering the whole meta-ecosystem.
Below, we simulate the behaviour of the model when environmental fertility conditions differ among local ecosystems. Let’s start with the case of higher environmental fertility in the donor ecosystem.
We begin by increasing the environmental fertility in the donor ecosystem, so that I1 > I2. As before, we will simulate three scenarios for producers’ recycling rates: equal, higher in ecosystem 1, and higher in ecosystem 2.
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 < I2
I1 = 18
I2 = 2
# leaching rate
l = 0.1
# Allocate an empty data frame to save the data in
PREDI1recrates <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDI1recrates <- rbind(PREDI1recrates, data.frame(TIME = seq(0, 100, 1),
hDIFF = seq(0, 100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1),
u2 = seq(0, 100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100, 1),
FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1), PROD_C2 = seq(0,
100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---in this scenario these
# are equal
h1 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 1---NOTE: it is equal to h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2---NOTE: it is equal to h1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDI1recrates[i1, ] <- c(i1, (h1 - h2), I1, I2, l, u1, u2, a1, a2,
h1, h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 + g)/(a1 *
e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (-a1 * e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) *
l * (c + m) + d2 * (-1 + e2) * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * (e2 * (d1 + g) * I2 * (c + m) * u1 - e1 *
(d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l -
d2 * (-1 + e2) * u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 *
e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 -
e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) * u2)/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 *
l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) * u2), (e1 * g *
(-h1 * l + I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)), ((d1 + g) * h1)/(a1 * e1), (d1 * e1 * (-h1 * l +
I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (h2 * (l *
(-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 *
g * m * (-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c +
m) * u1 - (d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 *
l - I1 * u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l -
I2 * u2)))/(a2 * e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) +
e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 *
e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 -
e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2) + (d2 * ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) + e2 *
(-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 * (-1 + e2) * u2),
(c * e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 +
a1 * e1 * I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 * (-1 +
e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1)))) * u2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2)))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2) * (a2 * e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 *
e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 *
(d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 *
d2 * e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) + d2 *
e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 * (c + m) *
u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) * (a2 * e2 * l - d2 * (-1 + e2) * u2)))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDI1recrates$Ntot <- PREDI1recrates$N1 + PREDI1recrates$N2
PREDI1recrates$Ptot <- PREDI1recrates$P1 + PREDI1recrates$P2
PREDI1recrates$Ctot <- PREDI1recrates$C1 + PREDI1recrates$C2
# now calculate recycling flux for the local ecosystem
PREDI1recrates$FLUX_Eco1_check <- PREDI1recrates$FLUX_P1 + PREDI1recrates$FLUX_C1
PREDI1recrates$FLUX_Eco2_check <- PREDI1recrates$FLUX_P2 + PREDI1recrates$FLUX_C2
# and the meta-ecosystem recycling flux
PREDI1recrates$FLUX_Ptot <- PREDI1recrates$FLUX_P1 + PREDI1recrates$FLUX_P2
PREDI1recrates$FLUX_Ctot <- PREDI1recrates$FLUX_C1 + PREDI1recrates$FLUX_C2
PREDI1recrates$MetaEcoFlux <- PREDI1recrates$FLUX_Eco_1 + PREDI1recrates$FLUX_Eco_2 -
PREDI1recrates$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDI1recrates <- dplyr::mutate(PREDI1recrates, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDI1recrates <- dplyr::mutate(PREDI1recrates, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDI1recrates dataset.
PREDI1recrates <- PREDI1recrates %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
# create an empty dataframe
MathStab_I1eqh <- NULL
for (i in 1:nrow(PREDI1recrates)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDI1recrates$I1[i]
I2 = PREDI1recrates$I2[i]
l = PREDI1recrates$l[i]
u1 = PREDI1recrates$u1[i]
u2 = PREDI1recrates$u2[i]
a1 = PREDI1recrates$a1[i]
a2 = PREDI1recrates$a2[i]
h1 = PREDI1recrates$h1[i]
h2 = PREDI1recrates$h2[i]
d1 = PREDI1recrates$d1[i]
d2 = PREDI1recrates$d2[i]
g = PREDI1recrates$g[i]
m = PREDI1recrates$m[i]
c = PREDI1recrates$c[i]
e1 = PREDI1recrates$e1[i]
e2 = PREDI1recrates$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDI1recrates$N1[i]
dP1 = PREDI1recrates$P1[i]
dC1 = PREDI1recrates$C1[i]
dN2 = PREDI1recrates$N2[i]
dP2 = PREDI1recrates$P2[i]
dC2 = PREDI1recrates$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 * dC1 *
e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l - dP2 * u2,
h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 - h2 + dN2 *
u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 * dC2 * e2, -d2 +
a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_I1eqh <- rbind(MathStab_I1eqh, data.frame(TIME = i, I1, I2,
l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2, dN1, dP1, dC1,
dN2, dP2, dC2, EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEv = max(Re(base::eigen(Jacob)$values)), stable = stable, biosense = PREDI1recrates$biosense[i]))
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
# browser()
}
MathStab_I1eqh$stable <- as.factor(MathStab_I1eqh$stable)
# separate unstable equilibria to work with later
MathStab_I1eqhUS <- subset(MathStab_I1eqh, MathStab_I1eqh$stable == "unstable")
3.2% of the stability analyses runs produced unstable results (317 out of 10000). Let’s see if these are these also the same as those that return equilibria without biological sense (i.e., state variables values at equilibrium <0).
biononsense_I1eqh <- subset(PREDI1recrates, PREDI1recrates$biosense ==
"no")
identical(as.numeric(MathStab_I1eqhUS[, "TIME"]), biononsense_I1eqh[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDI1recrates.
Now, let’s exclude the unstable, biological nonsense parameter sets from the analyses below and from our results, and then randomly sample 1000 iterations to use in producing figures and to compare with other scenarios.
PREDI1recrates_pos <- filter(PREDI1recrates, PREDI1recrates$biosense ==
"yes" | PREDI1recrates$stable == "stable")
# sample PREDpos to only use 1000 random simulation results
PREDI1recrates_sample1000 <- PREDI1recrates_pos[sample(nrow(PREDI1recrates_pos),
size = 1000), ]
PREDI1recrates_1k <- droplevels(PREDI1recrates_sample1000)
# pivot the data frame containing the predictions
PREDI1recrates_biomass_long <- pivot_longer(PREDI1recrates_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDI1recrates_biomass_long$Compartment <- as_factor(PREDI1recrates_biomass_long$Compartment)
PREDI1recrates_biomass_long$Ecosystem <- as_factor(PREDI1recrates_biomass_long$Ecosystem)
comparts_medians <- PREDI1recrates_biomass_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data=PREDI1recrates_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 28: Differing primary producers’ recycling rates do not appreciably change the median nutrient stock and median biomass values at either local or meta-ecosystem scale (compare with Figure 18).
FluxPREDI1recrates <- PREDI1recrates_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDI1recrates$Scale <- as.factor(FluxPREDI1recrates$Scale)
FluxPREDI1recrates$Compartment <- as.factor(FluxPREDI1recrates$Compartment)
comparts_medians <- FluxPREDI1recrates %>% dplyr::group_by(Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDI1recrates %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(0, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Figure 29: Nutrient flux median values in all trophic compartment at the two scales of interest, local and meta-ecosystem. When h1 = h2 and consumers move from a more to a less fertile ecosystem, nutrient flux at the meta-ecosystem scale is much lower than in the corresponding case when local fertility is equivalent between donor and recipient ecosystem (Figure 23).
Here, we will use the same set of increasing percentage stored in hdiff to investigate how productivity and nutrient flux vary at local and meta-ecosystem scale when material lost from producers in ecosystem 1 is more recycling-available than in ecosystem 2.
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 > I2
I1 = 18
I2 = 2
# leaching rate
l = 0.1
# allocate empty dataframe to store simulations results
PREDhigh_I1h1 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtemp <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDtemp <- rbind(PREDtemp, data.frame(TIME = seq(0, 100, 1), hDIFF = seq(0,
100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1), u2 = seq(0,
100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100,
1), FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1),
PROD_C2 = seq(0, 100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---h1 > h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2
h1 = h2 + (h2 * hdiff[j]) # death (=recycling) rate for producers in ecosystem 1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDtemp[i1, ] <- c(i1, hdiff[j], I1, I2, l, u1, u2, a1, a2, h1,
h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 +
g)/(a1 * e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 +
g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 *
e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 *
e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), (e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * h1)/(a1 * e1),
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 * g *
m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 *
e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) *
h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (c * e1 * g * (-h1 * l +
I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 + a1 * e1 *
I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 +
e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 *
u2)))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) *
(a2 * e2 * l - d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l *
(c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 * d2 *
e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) +
d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)))
}
# store the results from the j-th round of simulations into the
# persistent dataframe
PREDhigh_I1h1 <- rbind(PREDhigh_I1h1, PREDtemp)
# print the percentage used in this round of simulations
# print(hdiff[j])
# remove the temporary dataframe
rm(PREDtemp)
# browser()
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDhigh_I1h1$Ntot <- PREDhigh_I1h1$N1 + PREDhigh_I1h1$N2
PREDhigh_I1h1$Ptot <- PREDhigh_I1h1$P1 + PREDhigh_I1h1$P2
PREDhigh_I1h1$Ctot <- PREDhigh_I1h1$C1 + PREDhigh_I1h1$C2
# now calculate recycling flux for the local ecosystem
PREDhigh_I1h1$FLUX_Eco1_check <- PREDhigh_I1h1$FLUX_P1 + PREDhigh_I1h1$FLUX_C1
PREDhigh_I1h1$FLUX_Eco2_check <- PREDhigh_I1h1$FLUX_P2 + PREDhigh_I1h1$FLUX_C2
# and the meta-ecosystem recycling flux
PREDhigh_I1h1$FLUX_Ptot <- PREDhigh_I1h1$FLUX_P1 + PREDhigh_I1h1$FLUX_P2
PREDhigh_I1h1$FLUX_Ctot <- PREDhigh_I1h1$FLUX_C1 + PREDhigh_I1h1$FLUX_C2
PREDhigh_I1h1$MetaEcoFlux <- PREDhigh_I1h1$FLUX_Eco_1 + PREDhigh_I1h1$FLUX_Eco_2 -
PREDhigh_I1h1$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDhigh_I1h1 <- dplyr::mutate(PREDhigh_I1h1, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDhigh_I1h1 <- dplyr::mutate(PREDhigh_I1h1, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDhigh_I1h1 dataset.
PREDhigh_I1h1 <- PREDhigh_I1h1 %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
Here we run stability analyses for the scenario of higher environmental fertility and higher primary producers recycling rates in ecosystem 1.
# We will store all the stability analyses for the various levels of
# hdiff here
MathStab_I1h1 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtempSA <- subset(PREDhigh_I1h1, PREDhigh_I1h1$hDIFF == hdiff[j])
MathStab_temp <- NULL
MathStab_temp <- rbind(MathStab_temp, data.frame(TIME = seq(0, 100,
1), hDIFF = seq(0, 100, 1), I1 = seq(0, 100, 1), I2 = seq(0, 100,
1), l = seq(0, 100, 1), u1 = seq(0, 100, 1), u2 = seq(0, 100, 1),
a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0, 100, 1),
h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0, 100, 1),
g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100, 1), e1 = seq(0,
100, 1), e2 = seq(0, 100, 1), EiV1 = seq(0, 100, 1), EiV2 = seq(0,
100, 1), EiV3 = seq(0, 100, 1), EiV4 = seq(0, 100, 1), EiV5 = seq(0,
100, 1), EiV6 = seq(0, 100, 1), maxEV = seq(0, 100, 1), biosense = NA,
stable = NA))
for (i in 1:nrow(PREDtempSA)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDtempSA$I1[i]
I2 = PREDtempSA$I2[i]
l = PREDtempSA$l[i]
u1 = PREDtempSA$u1[i]
u2 = PREDtempSA$u2[i]
a1 = PREDtempSA$a1[i]
a2 = PREDtempSA$a2[i]
h1 = PREDtempSA$h1[i]
h2 = PREDtempSA$h2[i]
d1 = PREDtempSA$d1[i]
d2 = PREDtempSA$d2[i]
g = PREDtempSA$g[i]
m = PREDtempSA$m[i]
c = PREDtempSA$c[i]
e1 = PREDtempSA$e1[i]
e2 = PREDtempSA$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDtempSA$N1[i]
dP1 = PREDtempSA$P1[i]
dC1 = PREDtempSA$C1[i]
dN2 = PREDtempSA$N2[i]
dP2 = PREDtempSA$P2[i]
dC2 = PREDtempSA$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 *
dC1 * e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l -
dP2 * u2, h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 -
h2 + dN2 * u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 *
dC2 * e2, -d2 + a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_temp[i, ] <- c(TIME = i, hDIFF = PREDtempSA$hDIFF[i],
I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEV = max(Re(base::eigen(Jacob)$values)), biosense = as.character(PREDtempSA$biosense[i]),
stable = stable)
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
}
# now store everything in the dataframe created earlier, for ease of
# reference
MathStab_I1h1 <- rbind(MathStab_I1h1, MathStab_temp)
# browser()
# remove the temporary dataframe to avoid mistakes
rm(MathStab_temp)
}
# remove PREDtempSA to avoid mistakes in later use of similarly named
# objects
rm(PREDtempSA)
MathStab_I1h1[, c(1:25)] <- lapply(MathStab_I1h1[, c(1:25)], as.numeric)
MathStab_I1h1[, 26:27] <- lapply(MathStab_I1h1[, 26:27], as.factor)
# separate unstable equilibria to work with later
MathStab_I1h1US <- subset(MathStab_I1h1, MathStab_I1h1$stable == "unstable")
In this scenario, 3.4% of the stability analyses runs produced unstable results (1683 out of 50000 iterations). Let’s check if these are also the equilibria with no biological sense (i.e., state variable values <0).
biononsense_I1h1 <- subset(PREDhigh_I1h1, PREDhigh_I1h1$biosense == "no")
identical(as.numeric(MathStab_I1h1US[, "TIME"]), biononsense_I1h1[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDhigh_I1h1. First, we need to edit column TIME in PREDhigh_I1h1 and MathStab_I1h1 to be real indexing column. By this, we mean that it currently has repeated entries because it was populated with i1 in the loop above. We are going to copy the rownames of the dataframes to TIME, thus turning it in a real indexing variable.
PREDhigh_I1h1$TIME <- as.numeric(rownames(PREDhigh_I1h1))
MathStab_I1h1$TIME <- as.numeric(rownames(MathStab_I1h1))
PREDhigh_I1h1 <- left_join(PREDhigh_I1h1, select(MathStab_I1h1, !c(hDIFF:biosense)),
by = "TIME")
Now, let’s exclude the unstable, biological nonsense parameter sets that produce these equilibria that are both unstable and devoid of biological sense from the analyses below and from our results. We then sample 1000 random iterations from the PREDhigh_I1h1 dataset to use when creating figures and comparing with other scenarios.
PREDhigh_I1h1pos <- filter(PREDhigh_I1h1, PREDhigh_I1h1$biosense == "yes" |
PREDhigh_h1$stable == "stable")
PREDhigh_I1h1pos$hDIFF <- as.factor(PREDhigh_I1h1pos$hDIFF)
PREDhigh_I1h1_1k <- NULL
for (i in 1:length(hdiff)) {
temp <- subset(PREDhigh_I1h1pos, PREDhigh_I1h1pos$hDIFF == hdiff[i])
temp1000 <- temp[sample(nrow(temp), size = 1000), ]
PREDhigh_I1h1_1k <- rbind(PREDhigh_I1h1_1k, temp1000)
print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
PREDhigh_I1h1_1k <- droplevels(PREDhigh_I1h1_1k)
rm(temp, temp1000)
# pivot the data frame containing the predictions
PREDhigh_I1h1_biomass_long <- pivot_longer(PREDhigh_I1h1_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDhigh_I1h1_biomass_long$Compartment <- as_factor(PREDhigh_I1h1_biomass_long$Compartment)
PREDhigh_I1h1_biomass_long$Ecosystem <- as_factor(PREDhigh_I1h1_biomass_long$Ecosystem)
PREDhigh_I1h1_biomass_long$hDIFF <- as_factor(PREDhigh_I1h1_biomass_long$hDIFF)
comparts_medians <- PREDhigh_I1h1_biomass_long %>% dplyr::group_by(hDIFF, Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data = PREDhigh_I1h1_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(-15, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 30: As h1 increases compared to h2, median nutrient stock in ecosystem 1 does too. In turn, this leads to a higher median nutrient stock value at the meta-ecosystem scale.
FluxPREDhigh_I1h1 <- PREDhigh_I1h1_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDhigh_I1h1$Scale <- as.factor(FluxPREDhigh_I1h1$Scale)
FluxPREDhigh_I1h1$Compartment <- as.factor(FluxPREDhigh_I1h1$Compartment)
FluxPREDhigh_I1h1$hDIFF <- as.factor(FluxPREDhigh_I1h1$hDIFF)
comparts_medians <- FluxPREDhigh_I1h1 %>% dplyr::group_by(hDIFF, Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDhigh_I1h1 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(-20, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Figure 31: As producers’ recycling rate increase, so does median nutrient flux in ecosystem 1. In turn, the whole meta-ecosystem experiences heightened median values of nutrient flux.
Here, when environmental fertility is higher in ecosystem 1, producers are less recycling-available and recycling of lost producers biomass is easier in ecosystem 2 (the recipient).
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 = I2
I1 = 18
I2 = 2
# leaching rate
l = 0.1
# allocate empty dataframe to store simulations results
PREDhigh_I1h2 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtemp <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDtemp <- rbind(PREDtemp, data.frame(TIME = seq(0, 100, 1), hDIFF = seq(0,
100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1), u2 = seq(0,
100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100,
1), FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1),
PROD_C2 = seq(0, 100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---h1 < h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2
h1 = h2 - (h2 * hdiff[j]) # death (=recycling) rate for producers in ecosystem 1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDtemp[i1, ] <- c(i1, hdiff[j], I1, I2, l, u1, u2, a1, a2, h1,
h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 +
g)/(a1 * e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 +
g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 *
e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 *
e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), (e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * h1)/(a1 * e1),
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 * g *
m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 *
e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) *
h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (c * e1 * g * (-h1 * l +
I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 + a1 * e1 *
I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 +
e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 *
u2)))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) *
(a2 * e2 * l - d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l *
(c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 * d2 *
e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) +
d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)))
}
# store the results from the j-th round of simulations into the
# persistent dataframe
PREDhigh_I1h2 <- rbind(PREDhigh_I1h2, PREDtemp)
# print the percentage used in this round of simulations
# print(hdiff[j])
# remove the temporary dataframe
rm(PREDtemp)
# browser()
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDhigh_I1h2$Ntot <- PREDhigh_I1h2$N1 + PREDhigh_I1h2$N2
PREDhigh_I1h2$Ptot <- PREDhigh_I1h2$P1 + PREDhigh_I1h2$P2
PREDhigh_I1h2$Ctot <- PREDhigh_I1h2$C1 + PREDhigh_I1h2$C2
# now calculate recycling flux for the local ecosystem
PREDhigh_I1h2$FLUX_Eco1_check <- PREDhigh_I1h2$FLUX_P1 + PREDhigh_I1h2$FLUX_C1
PREDhigh_I1h2$FLUX_Eco2_check <- PREDhigh_I1h2$FLUX_P2 + PREDhigh_I1h2$FLUX_C2
# and the meta-ecosystem recycling flux
PREDhigh_I1h2$FLUX_Ptot <- PREDhigh_I1h2$FLUX_P1 + PREDhigh_I1h2$FLUX_P2
PREDhigh_I1h2$FLUX_Ctot <- PREDhigh_I1h2$FLUX_C1 + PREDhigh_I1h2$FLUX_C2
PREDhigh_I1h2$MetaEcoFlux <- PREDhigh_I1h2$FLUX_Eco_1 + PREDhigh_I1h2$FLUX_Eco_2 -
PREDhigh_I1h2$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDhigh_I1h2 <- dplyr::mutate(PREDhigh_I1h2, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDhigh_I1h2 <- dplyr::mutate(PREDhigh_I1h2, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDhigh_I1h2 dataset.
PREDhigh_I1h2 <- PREDhigh_I1h2 %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
Here we run stability analyses for this scenario of high environmental fertility in ecosystem 1 but high primary producers recycling rates in ecosystem 2.
# We will store all the stability analyses for the various levels of
# hdiff here
MathStab_I1h2 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtempSA <- subset(PREDhigh_I1h2, PREDhigh_I1h2$hDIFF == hdiff[j])
MathStab_temp <- NULL
MathStab_temp <- rbind(MathStab_temp, data.frame(TIME = seq(0, 100,
1), hDIFF = seq(0, 100, 1), I1 = seq(0, 100, 1), I2 = seq(0, 100,
1), l = seq(0, 100, 1), u1 = seq(0, 100, 1), u2 = seq(0, 100, 1),
a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0, 100, 1),
h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0, 100, 1),
g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100, 1), e1 = seq(0,
100, 1), e2 = seq(0, 100, 1), EiV1 = seq(0, 100, 1), EiV2 = seq(0,
100, 1), EiV3 = seq(0, 100, 1), EiV4 = seq(0, 100, 1), EiV5 = seq(0,
100, 1), EiV6 = seq(0, 100, 1), maxEV = seq(0, 100, 1), biosense = NA,
stable = NA))
for (i in 1:nrow(PREDtempSA)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDtempSA$I1[i]
I2 = PREDtempSA$I2[i]
l = PREDtempSA$l[i]
u1 = PREDtempSA$u1[i]
u2 = PREDtempSA$u2[i]
a1 = PREDtempSA$a1[i]
a2 = PREDtempSA$a2[i]
h1 = PREDtempSA$h1[i]
h2 = PREDtempSA$h2[i]
d1 = PREDtempSA$d1[i]
d2 = PREDtempSA$d2[i]
g = PREDtempSA$g[i]
m = PREDtempSA$m[i]
c = PREDtempSA$c[i]
e1 = PREDtempSA$e1[i]
e2 = PREDtempSA$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDtempSA$N1[i]
dP1 = PREDtempSA$P1[i]
dC1 = PREDtempSA$C1[i]
dN2 = PREDtempSA$N2[i]
dP2 = PREDtempSA$P2[i]
dC2 = PREDtempSA$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 *
dC1 * e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l -
dP2 * u2, h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 -
h2 + dN2 * u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 *
dC2 * e2, -d2 + a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_temp[i, ] <- c(TIME = i, hDIFF = PREDtempSA$hDIFF[i],
I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEV = max(Re(base::eigen(Jacob)$values)), biosense = as.character(PREDtempSA$biosense[i]),
stable = stable)
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
}
# now store everything in the dataframe created earlier, for ease of
# reference
MathStab_I1h2 <- rbind(MathStab_I1h2, MathStab_temp)
# browser()
# remove the temporary dataframe to avoid mistakes
rm(MathStab_temp)
}
# remove PREDtempSA to avoid mistakes in later use of similarly named
# objects
rm(PREDtempSA)
MathStab_I1h2[, c(1:25)] <- lapply(MathStab_I1h2[, c(1:25)], as.numeric)
MathStab_I1h2[, 26:27] <- lapply(MathStab_I1h2[, 26:27], as.factor)
# separate unstable equilibria to work with later
MathStab_I1h2US <- subset(MathStab_I1h2, MathStab_I1h2$stable == "unstable")
In this scenario, 3.1% of the stability analyses runs produced unstable results (i.e., 1537 out of 50000 iterations). Let’s check if these are also the equilibria with no biological sense (i.e., state variable values <0).
biononsense_I1h2 <- subset(PREDhigh_I1h2, PREDhigh_I1h2$biosense == "no")
identical(as.numeric(MathStab_I1h2US[, "TIME"]), biononsense_I1h2[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDhigh_I1h2. First, we need to edit column TIME in PREDhigh_I1h2 and MathStab_I1h2 to be real indexing column. By this, we mean that it currently has repeated entries because it was populated with i1 in the loop above. We are going to copy the rownames of the dataframes to TIME, thus turning it in a real indexing variable.
PREDhigh_I1h2$TIME <- as.numeric(rownames(PREDhigh_I1h2))
MathStab_I1h2$TIME <- as.numeric(rownames(MathStab_I1h2))
PREDhigh_I1h2 <- left_join(PREDhigh_I1h2, select(MathStab_I1h2, !c(hDIFF:biosense)),
by = "TIME")
Now, let’s exclude the unstable, biological nonsense parameter sets that produce these equilibria that are both unstable and devoid of biological sense from the analyses below and from our results. We then randomly sample 1000 iterations to use in creating figures and comparing to other scenarios later on.
PREDhigh_I1h2pos <- filter(PREDhigh_I1h2, PREDhigh_I1h2$biosense == "yes" |
PREDhigh_I1h2$stable == "stable")
PREDhigh_I1h2pos$hDIFF <- as.factor(PREDhigh_I1h2pos$hDIFF)
PREDhigh_I1h2_1k <- NULL
for (i in 1:length(hdiff)) {
temp <- subset(PREDhigh_I1h2pos, PREDhigh_I1h2pos$hDIFF == hdiff[i])
temp1000 <- temp[sample(nrow(temp), size = 1000), ]
PREDhigh_I1h2_1k <- rbind(PREDhigh_I1h2_1k, temp1000)
print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
PREDhigh_I1h2_1k <- droplevels(PREDhigh_I1h2_1k)
rm(temp, temp1000)
# pivot the data frame containing the predictions
PREDhigh_I1h2_biomass_long <- pivot_longer(PREDhigh_I1h2_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDhigh_I1h2_biomass_long$Compartment <- as_factor(PREDhigh_I1h2_biomass_long$Compartment)
PREDhigh_I1h2_biomass_long$Ecosystem <- as_factor(PREDhigh_I1h2_biomass_long$Ecosystem)
PREDhigh_I1h2_biomass_long$hDIFF <- as_factor(PREDhigh_I1h2_biomass_long$hDIFF)
comparts_medians <- PREDhigh_I1h2_biomass_long %>% dplyr::group_by(hDIFF, Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data = PREDhigh_I1h2_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(-15, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 32: Reduced recycling rates for producers in ecosystem 1 lead a decrease un nutrient stocks at both local and meta-ecosystem scales.
FluxPREDhigh_I1h2 <- PREDhigh_I1h2_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDhigh_I1h2$Scale <- as.factor(FluxPREDhigh_I1h2$Scale)
FluxPREDhigh_I1h2$Compartment <- as.factor(FluxPREDhigh_I1h2$Compartment)
FluxPREDhigh_I1h2$hDIFF <- as.factor(FluxPREDhigh_I1h2$hDIFF)
comparts_medians <- FluxPREDhigh_I1h2 %>% dplyr::group_by(hDIFF, Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDhigh_I1h2 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines")) +
facet_grid(hDIFF~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(-35, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Figure 33: Producers’ nutrient flux in ecosystem 1 decreases following reduced recycling rates, compared to ecosystem 2. In turn, this leads to a reduction of nutrient flux at both local and meta-ecosystem scales.
Here, we investigate how varying the relative magnitude of producers’ recycling rates (hi, i \(\in [1,2]\)) in the local ecosystems influences local and meta-ecosystem functions, when the recipient ecosystem is more fertile than the donor. Hence, this is a test of the interaction between producers’ recycling rates and non-diffusive, against-gradient consumer movement. As in previous simulations, we begin by testing the case of h1 = h2, then the case of h1 > h2, and finally the case of h1 < h2.
As a baseline case, here we simulate the case of h1 = h2 while environmental fertility is much higher in the recipient ecosystem compared to the donor.
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 < I2
I1 = 2
I2 = 18
# leaching rate
l = 0.1
# Allocate an empty data frame to save the data in
PREDI2recrates <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDI2recrates <- rbind(PREDI2recrates, data.frame(TIME = seq(0, 100, 1),
hDIFF = seq(0, 100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1),
u2 = seq(0, 100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100, 1),
FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1), PROD_C2 = seq(0,
100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---in this scenario these
# are equal
h1 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 1---NOTE: it is equal to h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2---NOTE: it is equal to h1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDI2recrates[i1, ] <- c(i1, (h1 - h2), I1, I2, l, u1, u2, a1, a2,
h1, h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 + g)/(a1 *
e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (-a1 * e1 * (d2 * (-1 + e2) * h2 - a2 * e2 * I2) *
l * (c + m) + d2 * (-1 + e2) * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * (e2 * (d1 + g) * I2 * (c + m) * u1 - e1 *
(d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 * u1))))/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) * (a2 * e2 * l -
d2 * (-1 + e2) * u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 *
e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 -
e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) * u2)/((c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 *
l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) * u2), (e1 * g *
(-h1 * l + I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)), ((d1 + g) * h1)/(a1 * e1), (d1 * e1 * (-h1 * l +
I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (h2 * (l *
(-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 *
g * m * (-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c +
m) * u1 - (d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 *
l - I1 * u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l -
I2 * u2)))/(a2 * e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) +
e2 * (d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c +
m) * u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 * e1 *
e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c + m) * u1 -
e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2) + (d2 * ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) + e2 *
(-h2 * l + I2 * u2)))/(a2 * e2 * l - d2 * (-1 + e2) * u2),
(c * e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 * l + (d1 -
d1 * e1 + g) * u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 +
a1 * e1 * I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 +
g) * u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 * (-1 +
e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1)))) * u2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 *
(d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2)))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2) * (a2 * e2 * h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 *
e1 + g) * u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 *
(d1 + g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 *
d2 * e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) + d2 *
e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 * (c + m) *
u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m * (h1 * l - I1 *
u1))) * u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) * (a2 * e2 * l - d2 * (-1 + e2) * u2)))
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDI2recrates$Ntot <- PREDI2recrates$N1 + PREDI2recrates$N2
PREDI2recrates$Ptot <- PREDI2recrates$P1 + PREDI2recrates$P2
PREDI2recrates$Ctot <- PREDI2recrates$C1 + PREDI2recrates$C2
# now calculate recycling flux for the local ecosystem
PREDI2recrates$FLUX_Eco1_check <- PREDI2recrates$FLUX_P1 + PREDI2recrates$FLUX_C1
PREDI2recrates$FLUX_Eco2_check <- PREDI2recrates$FLUX_P2 + PREDI2recrates$FLUX_C2
# and the meta-ecosystem recycling flux
PREDI2recrates$FLUX_Ptot <- PREDI2recrates$FLUX_P1 + PREDI2recrates$FLUX_P2
PREDI2recrates$FLUX_Ctot <- PREDI2recrates$FLUX_C1 + PREDI2recrates$FLUX_C2
PREDI2recrates$MetaEcoFlux <- PREDI2recrates$FLUX_Eco_1 + PREDI2recrates$FLUX_Eco_2 -
PREDI2recrates$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDI2recrates <- dplyr::mutate(PREDI2recrates, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDI2recrates <- dplyr::mutate(PREDI2recrates, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDI2recrates dataset.
PREDI2recrates <- PREDI2recrates %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
First, as usual, we run stability analyses by using the numerical method of estimating the model’s Jacobian matrix.
# create an empty dataframe
MathStab_I2eqh <- NULL
for (i in 1:nrow(PREDI2recrates)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDI2recrates$I1[i]
I2 = PREDI2recrates$I2[i]
l = PREDI2recrates$l[i]
u1 = PREDI2recrates$u1[i]
u2 = PREDI2recrates$u2[i]
a1 = PREDI2recrates$a1[i]
a2 = PREDI2recrates$a2[i]
h1 = PREDI2recrates$h1[i]
h2 = PREDI2recrates$h2[i]
d1 = PREDI2recrates$d1[i]
d2 = PREDI2recrates$d2[i]
g = PREDI2recrates$g[i]
m = PREDI2recrates$m[i]
c = PREDI2recrates$c[i]
e1 = PREDI2recrates$e1[i]
e2 = PREDI2recrates$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDI2recrates$N1[i]
dP1 = PREDI2recrates$P1[i]
dC1 = PREDI2recrates$C1[i]
dN2 = PREDI2recrates$N2[i]
dP2 = PREDI2recrates$P2[i]
dC2 = PREDI2recrates$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 * dC1 *
e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l - dP2 * u2,
h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 - h2 + dN2 *
u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 * dC2 * e2, -d2 +
a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_I2eqh <- rbind(MathStab_I2eqh, data.frame(TIME = i, I1, I2,
l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2, dN1, dP1, dC1,
dN2, dP2, dC2, EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEv = max(Re(base::eigen(Jacob)$values)), stable = stable, biosense = PREDI2recrates$biosense[i]))
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
# browser()
}
MathStab_I2eqh$stable <- as.factor(MathStab_I2eqh$stable)
# separate unstable equilibria to work with later
MathStab_I2eqhUS <- subset(MathStab_I2eqh, MathStab_I2eqh$stable == "unstable")
It appears that the two methods agree. 2.8% of the stability analyses runs produced unstable results (282 out of 10000). Let’s see if these are these also the same as those that return equilibria without biological sense (i.e., state variables values at equilibrium <0).
biononsense_I2eqh <- subset(PREDI2recrates, PREDI2recrates$biosense ==
"no")
identical(as.numeric(MathStab_I2eqhUS[, "TIME"]), biononsense_I2eqh[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDI2recrates.
Now, let’s exclude the unstable, biological nonsense parameter sets from the analyses below and from our results. Then we will sample 1000 random iterations from the ones that are left to use in producing figures and making comparisons with other scenarios.
PREDI2recrates_pos <- filter(PREDI2recrates, PREDI2recrates$biosense ==
"yes" | PREDI2recrates$stable == "stable")
# sample PREDpos to only use 1000 random simulation results
PREDI2recrates_sample1000 <- PREDI2recrates_pos[sample(nrow(PREDI2recrates_pos),
size = 1000), ]
PREDI2recrates_1k <- droplevels(PREDI2recrates_sample1000)
# pivot the data frame containing the predictions
PREDI2recrates_biomass_long <- pivot_longer(PREDI2recrates_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDI2recrates_biomass_long$Compartment <- as_factor(PREDI2recrates_biomass_long$Compartment)
PREDI2recrates_biomass_long$Ecosystem <- as_factor(PREDI2recrates_biomass_long$Ecosystem)
comparts_medians <- PREDI2recrates_biomass_long %>% dplyr::group_by(Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data=PREDI2recrates_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(0, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 34: Equal recycling rates for primary producers in the local ecosystems lead to environmental fertility playing the larger role in determining local and meta-ecosystem stocks of nutrients and biomass, as seen in Figure 34 above.
FluxPREDI2recrates <- PREDI2recrates_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDI2recrates$Scale <- as.factor(FluxPREDI2recrates$Scale)
FluxPREDI2recrates$Compartment <- as.factor(FluxPREDI2recrates$Compartment)
comparts_medians <- FluxPREDI2recrates %>% dplyr::group_by(Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDI2recrates %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(.~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(0, 550)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Figure 35: Nutrient flux in local and meta-ecosystem, when producers have equal recycling rates and the recipient ecosystem (ecosystem 2) is much more fertile than the donor ecosystem.
Here, we investigate the case of higher recycling rate for producers in the donor ecosystem compared to the recipient, when fertility is higher in the latter. As above, we use the formula \(h_1 = h_2 + (h_2 \cdot x)\), with \(x \in [0.1, 0.9]\), to calculate the difference between the recycling rates.
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 < I2
I1 = 2
I2 = 18
# leaching rate
l = 0.1
# allocate empty dataframe to store simulations results
PREDhigh_I2h1 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtemp <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDtemp <- rbind(PREDtemp, data.frame(TIME = seq(0, 100, 1), hDIFF = seq(0,
100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1), u2 = seq(0,
100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100,
1), FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1),
PROD_C2 = seq(0, 100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---h1 > h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2
h1 = h2 + (h2 * hdiff[j]) # death (=recycling) rate for producers in ecosystem 1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDtemp[i1, ] <- c(i1, hdiff[j], I1, I2, l, u1, u2, a1, a2, h1,
h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 +
g)/(a1 * e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 +
g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 *
e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 *
e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), (e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * h1)/(a1 * e1),
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 * g *
m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 *
e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) *
h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (c * e1 * g * (-h1 * l +
I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 + a1 * e1 *
I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 +
e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 *
u2)))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) *
(a2 * e2 * l - d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l *
(c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 * d2 *
e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) +
d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)))
}
# store the results from the j-th round of simulations into the
# persistent dataframe
PREDhigh_I2h1 <- rbind(PREDhigh_I2h1, PREDtemp)
# print the percentage used in this round of simulations
# print(hdiff[j])
# remove the temporary dataframe
rm(PREDtemp)
# browser()
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDhigh_I2h1$Ntot <- PREDhigh_I2h1$N1 + PREDhigh_I2h1$N2
PREDhigh_I2h1$Ptot <- PREDhigh_I2h1$P1 + PREDhigh_I2h1$P2
PREDhigh_I2h1$Ctot <- PREDhigh_I2h1$C1 + PREDhigh_I2h1$C2
# now calculate recycling flux for the local ecosystem
PREDhigh_I2h1$FLUX_Eco1_check <- PREDhigh_I2h1$FLUX_P1 + PREDhigh_I2h1$FLUX_C1
PREDhigh_I2h1$FLUX_Eco2_check <- PREDhigh_I2h1$FLUX_P2 + PREDhigh_I2h1$FLUX_C2
# and the meta-ecosystem recycling flux
PREDhigh_I2h1$FLUX_Ptot <- PREDhigh_I2h1$FLUX_P1 + PREDhigh_I2h1$FLUX_P2
PREDhigh_I2h1$FLUX_Ctot <- PREDhigh_I2h1$FLUX_C1 + PREDhigh_I2h1$FLUX_C2
PREDhigh_I2h1$MetaEcoFlux <- PREDhigh_I2h1$FLUX_Eco_1 + PREDhigh_I2h1$FLUX_Eco_2 -
PREDhigh_I2h1$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDhigh_I2h1 <- dplyr::mutate(PREDhigh_I2h1, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDhigh_I2h1 <- dplyr::mutate(PREDhigh_I2h1, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDhigh_I2h1 dataset.
PREDhigh_I2h1 <- PREDhigh_I2h1 %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
# We will store all the stability analyses for the various levels of
# hdiff here
MathStab_I2h1 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtempSA <- subset(PREDhigh_I2h1, PREDhigh_I2h1$hDIFF == hdiff[j])
MathStab_temp <- NULL
MathStab_temp <- rbind(MathStab_temp, data.frame(TIME = seq(0, 100,
1), hDIFF = seq(0, 100, 1), I1 = seq(0, 100, 1), I2 = seq(0, 100,
1), l = seq(0, 100, 1), u1 = seq(0, 100, 1), u2 = seq(0, 100, 1),
a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0, 100, 1),
h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0, 100, 1),
g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100, 1), e1 = seq(0,
100, 1), e2 = seq(0, 100, 1), EiV1 = seq(0, 100, 1), EiV2 = seq(0,
100, 1), EiV3 = seq(0, 100, 1), EiV4 = seq(0, 100, 1), EiV5 = seq(0,
100, 1), EiV6 = seq(0, 100, 1), maxEV = seq(0, 100, 1), biosense = NA,
stable = NA))
for (i in 1:nrow(PREDtempSA)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDtempSA$I1[i]
I2 = PREDtempSA$I2[i]
l = PREDtempSA$l[i]
u1 = PREDtempSA$u1[i]
u2 = PREDtempSA$u2[i]
a1 = PREDtempSA$a1[i]
a2 = PREDtempSA$a2[i]
h1 = PREDtempSA$h1[i]
h2 = PREDtempSA$h2[i]
d1 = PREDtempSA$d1[i]
d2 = PREDtempSA$d2[i]
g = PREDtempSA$g[i]
m = PREDtempSA$m[i]
c = PREDtempSA$c[i]
e1 = PREDtempSA$e1[i]
e2 = PREDtempSA$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDtempSA$N1[i]
dP1 = PREDtempSA$P1[i]
dC1 = PREDtempSA$C1[i]
dN2 = PREDtempSA$N2[i]
dP2 = PREDtempSA$P2[i]
dC2 = PREDtempSA$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 *
dC1 * e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l -
dP2 * u2, h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 -
h2 + dN2 * u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 *
dC2 * e2, -d2 + a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_temp[i, ] <- c(TIME = i, hDIFF = PREDtempSA$hDIFF[i],
I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEV = max(Re(base::eigen(Jacob)$values)), biosense = as.character(PREDtempSA$biosense[i]),
stable = stable)
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
}
# now store everything in the dataframe created earlier, for ease of
# reference
MathStab_I2h1 <- rbind(MathStab_I2h1, MathStab_temp)
# browser()
# remove the temporary dataframe to avoid mistakes
rm(MathStab_temp)
}
# remove PREDtempSA to avoid mistakes in later use of similarly named
# objects
rm(PREDtempSA)
MathStab_I2h1[, c(1:25)] <- lapply(MathStab_I2h1[, c(1:25)], as.numeric)
MathStab_I2h1[, 26:27] <- lapply(MathStab_I2h1[, 26:27], as.factor)
# separate unstable equilibria to work with later
MathStab_I2h1US <- subset(MathStab_I2h1, MathStab_I2h1$stable == "unstable")
In this scenario, 4.2% of the stability analyses runs produced unstable results (i.e., 2079 out of 50000 iterations). Let’s check if these are also the equilibria with no biological sense (i.e., state variable values <0).
biononsense_I2h1 <- subset(PREDhigh_I2h1, PREDhigh_I2h1$biosense == "no")
identical(as.numeric(MathStab_I2h1US[, "TIME"]), biononsense_I2h1[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDhigh_I2h1. First, we need to edit column TIME in PREDhigh_I2h1 and MathStab_I2h1 to be real indexing column. By this, we mean that it currently has repeated entries because it was populated with i1 in the loop above. We are going to copy the rownames of the dataframes to TIME, thus turning it in a real indexing variable.
PREDhigh_I2h1$TIME <- as.numeric(rownames(PREDhigh_I2h1))
MathStab_I2h1$TIME <- as.numeric(rownames(MathStab_I2h1))
PREDhigh_I2h1 <- left_join(PREDhigh_I2h1, select(MathStab_I2h1, !c(hDIFF:biosense)),
by = "TIME")
Now, let’s exclude the unstable, biological nonsense parameter sets that produce these equilibria that are both unstable and devoid of biological sense from the analyses below and from our results. We then randomly sample the remaining iterations for 1000 iterations to use in producing figures and making comparisons with the other scenarios.
PREDhigh_I2h1pos <- filter(PREDhigh_I2h1, PREDhigh_I2h1$biosense == "yes" |
PREDhigh_I2h1$stable == "stable")
PREDhigh_I2h1pos$hDIFF <- as.factor(PREDhigh_I2h1pos$hDIFF)
PREDhigh_I2h1_1k <- NULL
for (i in 1:length(hdiff)) {
temp <- subset(PREDhigh_I2h1pos, PREDhigh_I2h1pos$hDIFF == hdiff[i])
temp1000 <- temp[sample(nrow(temp), size = 1000), ]
PREDhigh_I2h1_1k <- rbind(PREDhigh_I2h1_1k, temp1000)
print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
PREDhigh_I2h1_1k <- droplevels(PREDhigh_I2h1_1k)
rm(temp, temp1000)
# pivot the data frame containing the predictions
PREDhigh_I2h1_biomass_long <- pivot_longer(PREDhigh_I2h1_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDhigh_I2h1_biomass_long$Compartment <- as_factor(PREDhigh_I2h1_biomass_long$Compartment)
PREDhigh_I2h1_biomass_long$Ecosystem <- as_factor(PREDhigh_I2h1_biomass_long$Ecosystem)
PREDhigh_I2h1_biomass_long$hDIFF <- as_factor(PREDhigh_I2h1_biomass_long$hDIFF)
comparts_medians <- PREDhigh_I2h1_biomass_long %>% dplyr::group_by(hDIFF, Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data = PREDhigh_I2h1_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(-15, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 36: As h1 increases, compared to h2, median nutrient stock in the donor ecosystem increases, and so does at the meta-ecosystem scale. Interestingly, we note a small but constant increase in median consumers’ biomass in ecosystem 2 happening alongside the nutrient stock increase in ecosystem, which similarly leads to a smalll increase in consumers biomass at the meta-ecosystem scale.
FluxPREDhigh_I2h1 <- PREDhigh_I2h1_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDhigh_I2h1$Scale <- as.factor(FluxPREDhigh_I2h1$Scale)
FluxPREDhigh_I2h1$Compartment <- as.factor(FluxPREDhigh_I2h1$Compartment)
FluxPREDhigh_I2h1$hDIFF <- as.factor(FluxPREDhigh_I2h1$hDIFF)
comparts_medians <- FluxPREDhigh_I2h1 %>% dplyr::group_by(hDIFF, Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDhigh_I2h1 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(-20, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Finally, we investigate the case in which the recipient ecosystem (ecosystem 2) benefits from both higher environmental fertility and higher primary producers’ recycling rates. As before, we calculate the higher h2 in ecosystem 2 as \(h_1 = h_2 - (h_2 \cdot x)\), with \(x \in [0.1, 0.9]\).
# Here we set values for inorganic Nutrients input rate in each patch
# In this case, I1 < I2
I1 = 2
I2 = 18
# leaching rate
l = 0.1
# allocate empty dataframe to store simulations results
PREDhigh_I2h2 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtemp <- NULL
# Allocate some rows and columns. We will replace the current numbers
# with results below Data we are calculating are (bio)mass for soil
# nutrients (N1, N2), producers (P1, P2), consumers (C1,C2), disperses
# (Q)
PREDtemp <- rbind(PREDtemp, data.frame(TIME = seq(0, 100, 1), hDIFF = seq(0,
100, 1), I1 = I1, I2 = I2, l = l, u1 = seq(0, 100, 1), u2 = seq(0,
100, 1), a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0,
100, 1), h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0,
100, 1), g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100,
1), e1 = seq(0, 100, 1), e2 = seq(0, 100, 1), N1 = seq(0, 100,
1), P1 = seq(0, 100, 1), C1 = seq(0, 100, 1), N2 = seq(0, 100,
1), P2 = seq(0, 100, 1), C2 = seq(0, 100, 1), Q = seq(0, 100, 1),
FLUX_P1 = seq(0, 100, 1), FLUX_C1 = seq(0, 100, 1), FLUX_P2 = seq(0,
100, 1), FLUX_C2 = seq(0, 100, 1), FLUX_Eco_1 = seq(0, 100,
1), FLUX_Eco_2 = seq(0, 100, 1), FLUX_Q = seq(0, 100, 1), PROD_P1 = seq(0,
100, 1), PROD_C1 = seq(0, 100, 1), PROD_P2 = seq(0, 100, 1),
PROD_C2 = seq(0, 100, 1)))
# We want to start simulations at 0
i1 = 0
for (i in seq(0, 9999, 1)) {
# go to the next row
i1 = i1 + 1
# Let's sample the parameter values from DATA. We will sample these x
# times as defined by the i for loop above producers uptake rates
u1 = DATA1[i1, 2] # in ecosystem 1
u2 = DATA1[i1, 3] # in ecosystem 2
# consumers attack rates
a1 = DATA1[i1, 4]
a2 = DATA1[i1, 5]
# death rates (= recycling rate) for producers---h1 < h2
h2 = DATA1[i1, 6] # death (=recycling) rate for producers in ecosystem 2
h1 = h2 - (h2 * hdiff[j]) # death (=recycling) rate for producers in ecosystem 1
# death rates (= recycling rate) for consumers
d1 = DATA1[i1, 8] # death (=recycling) rate for consumers in ecosystem 1
d2 = DATA1[i1, 9] # death (=recycling) rate for consumers in ecosystem 2
c = DATA1[i1, 10] # death rate in the disperser's pool Q
# movement rate to the Disperser's pool Q
g = DATA1[i1, 11]
# movement rate from the Disperser's pool Q
m = DATA1[i1, 12]
# efficiency of consumers
e1 = DATA2[i1, 2] # in ecosystem 1
e2 = DATA2[i1, 3] # in ecosystem 2
PREDtemp[i1, ] <- c(i1, hdiff[j], I1, I2, l, u1, u2, a1, a2, h1,
h2, d1, d2, g, m, c, e1, e2, ((d1 - d1 * e1 + g) * h1 + a1 *
e1 * I1)/(a1 * e1 * l + (d1 - d1 * e1 + g) * u1), (d1 +
g)/(a1 * e1), (e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l +
(d1 - d1 * e1 + g) * u1), (-a1 * e1 * (d2 * (-1 + e2) *
h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 + g) *
I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 +
g * m * (h1 * l - I1 * u1))))/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)), (l * (-d2 * (d1 * (-1 + e1) - g) * h2 * (c + m) *
u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) + d2 * (d1 *
e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c + m) * u1 +
e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 * d2 * e1 *
l * (c + m) * (h2 * l - I2 * u2))/(a2 * e2 * h2 * l * (c +
m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 * (a1 *
e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 * (c +
m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g * m * (h1 *
l - I1 * u1))) * u2), ((e1 * g * m * (-h1 * l + I1 * u1) *
u2)/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1)) +
e2 * (-h2 * l + I2 * u2))/(a2 * e2 * l - d2 * (-1 + e2) *
u2), (e1 * g * (-h1 * l + I1 * u1))/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)), ((d1 + g) * h1)/(a1 * e1),
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 * e1 +
g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2), (d2 * ((e1 * g *
m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), ((d1 + g) * h1)/(a1 * e1) +
(d1 * e1 * (-h1 * l + I1 * u1))/(a1 * e1 * l + (d1 - d1 *
e1 + g) * u1), (h2 * (l * (-d2 * (d1 * (-1 + e1) - g) *
h2 * (c + m) * u1 + a2 * e1 * g * m * (-h1 * l + I1 * u1)) +
d2 * (d1 * e1 * I2 * (c + m) * u1 - (d1 + g) * I2 * (c +
m) * u1 + e1 * g * m * (h1 * l - I1 * u1)) * u2 + a1 *
d2 * e1 * l * (c + m) * (h2 * l - I2 * u2)))/(a2 * e2 *
h2 * l * (c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1) - a2 * (a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1))) * u2) + (d2 * ((e1 *
g * m * (-h1 * l + I1 * u1) * u2)/((c + m) * (a1 * e1 *
l + (d1 - d1 * e1 + g) * u1)) + e2 * (-h2 * l + I2 * u2)))/(a2 *
e2 * l - d2 * (-1 + e2) * u2), (c * e1 * g * (-h1 * l +
I1 * u1))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), ((d1 + g) * ((d1 - d1 * e1 + g) * h1 + a1 * e1 *
I1) * u1)/(a1 * e1 * (a1 * e1 * l + (d1 - d1 * e1 + g) *
u1)), (e1 * (d1 + g) * (-h1 * l + I1 * u1))/(a1 * e1 *
l + (d1 - d1 * e1 + g) * u1), ((-a1 * e1 * (d2 * (-1 +
e2) * h2 - a2 * e2 * I2) * l * (c + m) + d2 * (-1 + e2) *
(d1 * (-1 + e1) - g) * h2 * (c + m) * u1 + a2 * (e2 * (d1 +
g) * I2 * (c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) *
u1 + g * m * (h1 * l - I1 * u1)))) * u2 * (l * (-d2 * (d1 *
(-1 + e1) - g) * h2 * (c + m) * u1 + a2 * e1 * g * m *
(-h1 * l + I1 * u1)) + d2 * (d1 * e1 * I2 * (c + m) * u1 -
(d1 + g) * I2 * (c + m) * u1 + e1 * g * m * (h1 * l - I1 *
u1)) * u2 + a1 * d2 * e1 * l * (c + m) * (h2 * l - I2 *
u2)))/((c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) *
(a2 * e2 * l - d2 * (-1 + e2) * u2) * (a2 * e2 * h2 * l *
(c + m) * (a1 * e1 * l + (d1 - d1 * e1 + g) * u1) - a2 *
(a1 * e1 * e2 * I2 * l * (c + m) + e2 * (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * e2 * I2 * (c + m) * u1 + g *
m * (h1 * l - I1 * u1))) * u2)), (e2 * l * (-a1 * d2 *
e1 * h2 * l * (c + m) + d2 * (d1 * (-1 + e1) - g) * h2 *
(c + m) * u1 + a2 * e1 * g * m * (h1 * l - I1 * u1)) +
d2 * e2 * (a1 * e1 * I2 * l * (c + m) + (d1 + g) * I2 *
(c + m) * u1 - e1 * (d1 * I2 * (c + m) * u1 + g * m *
(h1 * l - I1 * u1))) * u2)/((c + m) * (a1 * e1 * l +
(d1 - d1 * e1 + g) * u1) * (a2 * e2 * l - d2 * (-1 + e2) *
u2)))
}
# store the results from the j-th round of simulations into the
# persistent dataframe
PREDhigh_I2h2 <- rbind(PREDhigh_I2h2, PREDtemp)
# print the percentage used in this round of simulations
# print(hdiff[j])
# remove the temporary dataframe
rm(PREDtemp)
# browser()
}
# calculate meta-ecosystem biomass stocks for each trophic compartment,
# a.k.a., meta-ecosystem productivity
PREDhigh_I2h2$Ntot <- PREDhigh_I2h2$N1 + PREDhigh_I2h2$N2
PREDhigh_I2h2$Ptot <- PREDhigh_I2h2$P1 + PREDhigh_I2h2$P2
PREDhigh_I2h2$Ctot <- PREDhigh_I2h2$C1 + PREDhigh_I2h2$C2
# now calculate recycling flux for the local ecosystem
PREDhigh_I2h2$FLUX_Eco1_check <- PREDhigh_I2h2$FLUX_P1 + PREDhigh_I2h2$FLUX_C1
PREDhigh_I2h2$FLUX_Eco2_check <- PREDhigh_I2h2$FLUX_P2 + PREDhigh_I2h2$FLUX_C2
# and the meta-ecosystem recycling flux
PREDhigh_I2h2$FLUX_Ptot <- PREDhigh_I2h2$FLUX_P1 + PREDhigh_I2h2$FLUX_P2
PREDhigh_I2h2$FLUX_Ctot <- PREDhigh_I2h2$FLUX_C1 + PREDhigh_I2h2$FLUX_C2
PREDhigh_I2h2$MetaEcoFlux <- PREDhigh_I2h2$FLUX_Eco_1 + PREDhigh_I2h2$FLUX_Eco_2 -
PREDhigh_I2h2$FLUX_Q
# finally, calculate the production for each compartment at
# meta-ecosystem level
PREDhigh_I2h2 <- dplyr::mutate(PREDhigh_I2h2, PROD_Ptot = PROD_P1 + PROD_P2,
.after = "PROD_C2")
PREDhigh_I2h2 <- dplyr::mutate(PREDhigh_I2h2, PROD_Ctot = PROD_C1 + PROD_C2,
.after = "PROD_Ptot")
# Equilibrium stocks less than or equal to 0 make no biological sense,
# so we will remove them from the PREDhigh_I2h2 dataset.
PREDhigh_I2h2 <- PREDhigh_I2h2 %>% dplyr::mutate(., biosense = ifelse(N1 >
0 & P1 > 0 & C1 > 0 & N2 > 0 & P2 > 0 & C2 > 0 & Q > 0, "yes", "no"),
.after = MetaEcoFlux) %>% dplyr::mutate_at(vars(biosense), factor)
# We will store all the stability analyses for the various levels of
# hdiff here
MathStab_I2h2 <- NULL
for (j in 1:length(hdiff)) {
# Allocate a temporary empty data frame to save the data in
PREDtempSA <- subset(PREDhigh_I2h2, PREDhigh_I2h2$hDIFF == hdiff[j])
MathStab_temp <- NULL
MathStab_temp <- rbind(MathStab_temp, data.frame(TIME = seq(0, 100,
1), hDIFF = seq(0, 100, 1), I1 = seq(0, 100, 1), I2 = seq(0, 100,
1), l = seq(0, 100, 1), u1 = seq(0, 100, 1), u2 = seq(0, 100, 1),
a1 = seq(0, 100, 1), a2 = seq(0, 100, 1), h1 = seq(0, 100, 1),
h2 = seq(0, 100, 1), d1 = seq(0, 100, 1), d2 = seq(0, 100, 1),
g = seq(0, 100, 1), m = seq(0, 100, 1), c = seq(0, 100, 1), e1 = seq(0,
100, 1), e2 = seq(0, 100, 1), EiV1 = seq(0, 100, 1), EiV2 = seq(0,
100, 1), EiV3 = seq(0, 100, 1), EiV4 = seq(0, 100, 1), EiV5 = seq(0,
100, 1), EiV6 = seq(0, 100, 1), maxEV = seq(0, 100, 1), biosense = NA,
stable = NA))
for (i in 1:nrow(PREDtempSA)) {
# fetch parameter values from the equilibrium simulations df
I1 = PREDtempSA$I1[i]
I2 = PREDtempSA$I2[i]
l = PREDtempSA$l[i]
u1 = PREDtempSA$u1[i]
u2 = PREDtempSA$u2[i]
a1 = PREDtempSA$a1[i]
a2 = PREDtempSA$a2[i]
h1 = PREDtempSA$h1[i]
h2 = PREDtempSA$h2[i]
d1 = PREDtempSA$d1[i]
d2 = PREDtempSA$d2[i]
g = PREDtempSA$g[i]
m = PREDtempSA$m[i]
c = PREDtempSA$c[i]
e1 = PREDtempSA$e1[i]
e2 = PREDtempSA$e2[i]
# Put in the solutions to the equilibrium values here:
dN1 = PREDtempSA$N1[i]
dP1 = PREDtempSA$P1[i]
dC1 = PREDtempSA$C1[i]
dN2 = PREDtempSA$N2[i]
dP2 = PREDtempSA$P2[i]
dC2 = PREDtempSA$C2[i]
# create the Jacobian from Mathematica NOTE: in transposing from
# Mathematica, the Jacobian is here assembled by row
Jacob <- rbind(c(-l - dP1 * u1, h1 - dN1 * u1, d1, 0, 0, 0), c(dP1 *
u1, -a1 * dC1 - h1 + dN1 * u1, -a1 * dP1, 0, 0, 0), c(0, a1 *
dC1 * e1, -d1 - g + a1 * e1 * dP1, 0, 0, 0), c(0, 0, 0, -l -
dP2 * u2, h2 - dN2 * u2, d2), c(0, 0, 0, dP2 * u2, -a2 * dC2 -
h2 + dN2 * u2, -a2 * dP2), c(0, 0, (g * m)/(c + m), 0, a2 *
dC2 * e2, -d2 + a2 * e2 * dP2))
# Check to see if the real part of the leading eigenvalue is less than
# 0 or not - if it is than the system is stable. To run with Rob's
# method for the leading eigenvalue, change the conditions of the if
# statement to max(Re(base::eigen(J)$values)) < 0
if (max(Re(base::eigen(Jacob)$values)) < 0) {
stable <- "stable"
} else {
stable <- "unstable"
}
MathStab_temp[i, ] <- c(TIME = i, hDIFF = PREDtempSA$hDIFF[i],
I1, I2, l, u1, u2, a1, a2, h1, h2, d1, d2, g, m, c, e1, e2,
EiV1 = Re(eigen(Jacob)$values[1]), EiV2 = Re(eigen(Jacob)$values[2]),
EiV3 = Re(eigen(Jacob)$values[3]), EiV4 = Re(eigen(Jacob)$values[4]),
EiV5 = Re(eigen(Jacob)$values[5]), EiV6 = Re(eigen(Jacob)$values[6]),
maxEV = max(Re(base::eigen(Jacob)$values)), biosense = as.character(PREDtempSA$biosense[i]),
stable = stable)
# if running with Rob's method for the leading eigenvalue, change maxEV
# to max(Re(base::eigen(J)$values)) print(i)
}
# now store everything in the dataframe created earlier, for ease of
# reference
MathStab_I2h2 <- rbind(MathStab_I2h2, MathStab_temp)
# browser()
# remove the temporary dataframe to avoid mistakes
rm(MathStab_temp)
}
# remove PREDtempSA to avoid mistakes in later use of similarly named
# objects
rm(PREDtempSA)
MathStab_I2h2[, c(1:25)] <- lapply(MathStab_I2h2[, c(1:25)], as.numeric)
MathStab_I2h2[, 26:27] <- lapply(MathStab_I2h2[, 26:27], as.factor)
# separate unstable equilibria to work with later
MathStab_I2h2US <- subset(MathStab_I2h2, MathStab_I2h2$stable == "unstable")
In this scenario, 1.6% of the stability analyses runs produced unstable results (i.e., 818 out of 50000 iterations). Let’s check if these are also the equilibria with no biological sense (i.e., state variable values <0).
biononsense_I2h2 <- subset(PREDhigh_I2h2, PREDhigh_I2h2$biosense == "no")
identical(as.numeric(MathStab_I2h2US[, "TIME"]), biononsense_I2h2[, "TIME"])
[1] TRUE
It appears that they are. Hence, let’s add the stable/unstable information to PREDhigh_I2h2. First, we need to edit column TIME in PREDhigh_I2h2 and MathStab_I2h2 to be real indexing column. By this, we mean that it currently has repeated entries because it was populated with i1 in the loop above. We are going to copy the rownames of the dataframes to TIME, thus turning it in a real indexing variable.
PREDhigh_I2h2$TIME <- as.numeric(rownames(PREDhigh_I2h2))
MathStab_I2h2$TIME <- as.numeric(rownames(MathStab_I2h2))
PREDhigh_I2h2 <- left_join(PREDhigh_I2h2, select(MathStab_I2h2, !c(hDIFF:biosense)),
by = "TIME")
Now, let’s exclude the unstable, biological nonsense parameter sets that produce these equilibria that are both unstable and devoid of biological sense from the analyses below and from our results. Finally, we randomly sample 1000 iterations to use in producing the figures and making comparisons with the other scenarios.
PREDhigh_I2h2pos <- filter(PREDhigh_I2h2, PREDhigh_I2h2$biosense == "yes" |
PREDhigh_I2h2$stable == "stable")
PREDhigh_I2h2pos$hDIFF <- as.factor(PREDhigh_I2h2pos$hDIFF)
PREDhigh_I2h2_1k <- NULL
for (i in 1:length(hdiff)) {
temp <- subset(PREDhigh_I2h2pos, PREDhigh_I2h2pos$hDIFF == hdiff[i])
temp1000 <- temp[sample(nrow(temp), size = 1000), ]
PREDhigh_I2h2_1k <- rbind(PREDhigh_I2h2_1k, temp1000)
print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
PREDhigh_I2h2_1k <- droplevels(PREDhigh_I2h2_1k)
rm(temp, temp1000)
# pivot the data frame containing the predictions
PREDhigh_I2h2_biomass_long <- pivot_longer(PREDhigh_I2h2_1k, names_to = "Compartment", values_to = "Stock", cols = c(N1, P1, C1, N2, P2, C2, Q, Ntot, Ptot, Ctot)) %>% dplyr::mutate(., Ecosystem = if_else(Compartment == "N1"|Compartment == "C1"|Compartment == "P1", "Donor", if_else(Compartment == "N2"|Compartment == "P2"|Compartment=="C2", "Recipient", if_else(Compartment == "Q", "Dispersers' Pool", "Meta-ecosystem")))) %>% dplyr::filter(., Ecosystem != "Dispersers' Pool")
PREDhigh_I2h2_biomass_long$Compartment <- as_factor(PREDhigh_I2h2_biomass_long$Compartment)
PREDhigh_I2h2_biomass_long$Ecosystem <- as_factor(PREDhigh_I2h2_biomass_long$Ecosystem)
PREDhigh_I2h2_biomass_long$hDIFF <- as_factor(PREDhigh_I2h2_biomass_long$hDIFF)
comparts_medians <- PREDhigh_I2h2_biomass_long %>% dplyr::group_by(hDIFF, Compartment, Ecosystem) %>% dplyr::summarise(., median = median(Stock), .groups = "keep")
ggplot(data = PREDhigh_I2h2_biomass_long, aes(x = Compartment, y = Stock, fill = Compartment, col = Compartment)) +
geom_boxjitter(alpha = 0.15, outlier.intersect = TRUE, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines"), label.size = 0.15) +
facet_grid(hDIFF~Ecosystem, scales = "free") +
scale_color_discreterainbow() +
scale_fill_discreterainbow() +
theme_pubr() +
coord_cartesian(y = c(-15, 100)) + # limits range of y-axis without affecting stats in plot
theme(legend.position = "none")
Figure 37: Higher fertility and primary producers recycling rates in the recipient ecosystem lead to reductions in the nutrient stocks and producers biomass of the donor ecosystem. As well, we note a limited inrease in the biomass of consumers in the recipient ecosystem.
FluxPREDhigh_I2h2 <- PREDhigh_I2h2_1k %>% dplyr::select(TIME:c, FLUX_P1:FLUX_Eco_2, MetaEcoFlux) %>% tidyr::pivot_longer(names_to = "Compartment", values_to = "Flux", cols = c(FLUX_P1:MetaEcoFlux)) %>% dplyr::mutate(Scale = if_else(Compartment == "FLUX_P1" | Compartment == "FLUX_C1" | Compartment == "FLUX_Eco_1", "Donor", if_else(Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment == "FLUX_Eco_2", "Recipient", "Meta-ecosystem"))) %>% dplyr::mutate(., Scale = fct_relevel(Scale, "Donor", "Recipient", "Meta-ecosystem"))
FluxPREDhigh_I2h2$Scale <- as.factor(FluxPREDhigh_I2h2$Scale)
FluxPREDhigh_I2h2$Compartment <- as.factor(FluxPREDhigh_I2h2$Compartment)
FluxPREDhigh_I2h2$hDIFF <- as.factor(FluxPREDhigh_I2h2$hDIFF)
comparts_medians <- FluxPREDhigh_I2h2 %>% dplyr::group_by(hDIFF, Compartment, Scale) %>% dplyr::summarise(., median = median(Flux), .groups = "keep")
FluxPREDhigh_I2h2 %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, c("FLUX_P1", "FLUX_C1", "FLUX_Eco_1", "FLUX_P2", "FLUX_C2", "FLUX_Eco_2", "MetaEcoFlux"))) %>%
ggplot(., aes(x = Compartment, y = Flux)) +
geom_boxjitter(outlier.intersect = TRUE, alpha = 0.1, outlier.shape = 25, jitter.shape = 21) +
# stat_summary(aes(label=round(..y..,2)), fun=median, geom="text", color = "black") + # sanity check on median calculations above
geom_label(data = comparts_medians, aes(y = median, label = round(median,2)), col = "black", size = 3, fill = "#FFFFFF", alpha = 0.75, nudge_x = -0.18, label.padding = unit(0.15, "lines")) +
facet_grid(hDIFF~Scale, scales = "free") +
theme_pubr() +
coord_cartesian(y = c(-35, 350)) + # limits range of y-axis without affecting stats in plot
scale_x_discrete(labels = c("FLUX_C1" = "C1", "FLUX_P1" = "P1", "FLUX_Eco_1" = "Donor", "FLUX_C2" = "C2", "FLUX_P2" = "P2", "FLUX_Eco_2" = "Recipient", "MetaEcoFlux" = "Meta-ecosystem")) +
theme_pubr() +
theme(legend.position = "none", axis.title.x = element_blank())
Figure 38: For nutrient flux, the combination of higher fertility and primary producers recycling rates in the recipient ecosystem translate in an reduction at both local and meta-ecosystem scales.
The code chunk below summarizes the results of the model’s numerous stability analyses, one each per scenario investigated, and produces Table C1 shown in Online Appendix C. As the table shows, a small portion (1.26–4.16%) of the parameter sets used to simulate the model’s behaviour at equilibrium produces unstable results. As detailed above, we exclude these unstable parameter sets from further investigations.
In the table, each row represents a different primary producers recycling rate conditions tested and rows are grouped by the environmental fertility conditions.
The table is saved in folder ../Results/`` as a.tex` file.
# summarise the number of stable and unstable parameter sets for each
# stability analysis run with the numerical approximation method and
# calculate the percentage of unstable parameter sets over the total
# iterations of the simulations
MStabSum <- MathStab %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Equal fertility",
Recycling = "Random") %>% pivot_wider(id_cols = c(stable, Fertility,
Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab)) * 100))
I2MStabSum <- MathStabI2 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 1",
Recycling = "Random") %>% pivot_wider(id_cols = c(stable, Fertility,
Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStabI2)) * 100))
I1MStabSum <- MathStabI1 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 2",
Recycling = "Random") %>% pivot_wider(id_cols = c(stable, Fertility,
Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStabI1)) * 100))
eqIeqhMStabSum <- MathStab_eqIeqh %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Equal fertility",
Recycling = "Equal") %>% pivot_wider(id_cols = c(stable, Fertility,
Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_eqIeqh)) * 100))
h1MStabSum <- MathStab_h1 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Equal fertility",
Recycling = "Higher in ecosystem 1") %>% pivot_wider(id_cols = c(stable,
Fertility, Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_h1)) * 100))
h2MStabSum <- MathStab_h2 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Equal fertility",
Recycling = "Higher in ecosystem 2", ) %>% pivot_wider(id_cols = c(stable,
Fertility, Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_h2)) * 100))
I1eqhMStabSum <- MathStab_I1eqh %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 1",
Recycling = "Equal") %>% pivot_wider(id_cols = c(stable, Fertility,
Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_I1eqh)) * 100))
I1h1MStabSum <- MathStab_I1h1 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 1",
Recycling = "Higher in ecosystem 1") %>% pivot_wider(id_cols = c(stable,
Fertility, Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_I1h1)) * 100))
I1h2MStabSum <- MathStab_I1h2 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 1",
Recycling = "Higher in ecosystem 2") %>% pivot_wider(id_cols = c(stable,
Fertility, Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_I1h2)) * 100))
I2eqhMStabSum <- MathStab_I2eqh %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 2",
Recycling = "Equal") %>% pivot_wider(id_cols = c(stable, Fertility,
Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_I2eqh)) * 100))
I2h1MStabSum <- MathStab_I2h1 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 2",
Recycling = "Higher in ecosystem 1") %>% pivot_wider(id_cols = c(stable,
Fertility, Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_I2h1)) * 100))
I2h2MStabSum <- MathStab_I2h2 %>% dplyr::group_by(., stable) %>% dplyr::summarise(.,
n = n(), .groups = "keep") %>% add_column(., Fertility = "Higher fertility in ecosystem 2",
Recycling = "Higher in ecosystem 2") %>% pivot_wider(id_cols = c(stable,
Fertility, Recycling), names_from = stable, values_from = n) %>% dplyr::mutate(.,
Percent = ((unstable/nrow(MathStab_I2h2)) * 100))
# Bind the objects generated above into a single dataframe and then
# produce a table that shows the number of stable and unstable
# parameter sets for each stability analysis method
SAsummary <- bind_rows(MStabSum, I2MStabSum, I1MStabSum, eqIeqhMStabSum,
h1MStabSum, h2MStabSum, I1eqhMStabSum, I1h1MStabSum, I1h2MStabSum,
I2eqhMStabSum, I2h1MStabSum, I2h2MStabSum) %>% ungroup() %>% gt(.,
groupname_col = "Fertility") %>% fmt_number(columns = 5, decimals = 2) %>%
cols_label(., Fertility = "Fertility", Recycling = "Recycling", stable = md("Stable"),
unstable = md("Unstable"), Percent = md("\\%")) %>% tab_header(title = "Summary of stability analyses, showing the number of Stable and Unstable equilibria, and the percentage of Unstable equilibria over the total number of iterations (n = 10000). Rows are grouped by environmental fertility gradient scenario, and show results for different primary producers recycling rates gradient scenarios.") %>%
tab_style(style = list(cell_text(weight = "bold")), locations = cells_row_groups()) %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
# save it
gtsave(SAsummary, filename = "../Results/StabilityAnalyses_SummaryTable.tex")
# view it
SAsummary
| Summary of stability analyses, showing the number of Stable and Unstable equilibria, and the percentage of Unstable equilibria over the total number of iterations (n = 10000). Rows are grouped by environmental fertility gradient scenario, and show results for different primary producers recycling rates gradient scenarios. | |||
|---|---|---|---|
| Recycling | Stable | Unstable | % |
| Equal fertility | |||
| Random | 9845 | 155 | 1.55 |
| Equal | 9847 | 153 | 1.53 |
| Higher in ecosystem 1 | 49122 | 878 | 1.76 |
| Higher in ecosystem 2 | 49371 | 629 | 1.26 |
| Higher fertility in ecosystem 1 | |||
| Random | 9676 | 324 | 3.24 |
| Equal | 9683 | 317 | 3.17 |
| Higher in ecosystem 1 | 48317 | 1683 | 3.37 |
| Higher in ecosystem 2 | 48463 | 1537 | 3.07 |
| Higher fertility in ecosystem 2 | |||
| Random | 9715 | 285 | 2.85 |
| Equal | 9718 | 282 | 2.82 |
| Higher in ecosystem 1 | 47921 | 2079 | 4.16 |
| Higher in ecosystem 2 | 49182 | 818 | 1.64 |
Here we summarize the above results and produce the figures shown in the Manuscript and Online Appendix D. First, we summarize the results we obtained in section Consumer movement and ecosystem functions above. We then do the same for the results from section Synergies between consumer movement and environmental context.
We present figures for both the raw data and the log response ratio (LRR) of state variable standing stock (henceforth, “stock”), nutrient flux, and primary and secondary productivity (henceforth, “productivity”). Our reference for calculating the LRR will be the results for the first scenario we ran, Simulating model behavior at equilibrium (henceforth, baseline). In the baseline scenario, environmental fertility is homogeneous between local ecosystems (i.e., I1 = I2 = 10) and values for all other parameters in the model are selected at random (see above for details).
Mathematically, the ratio’s formula is:
\(\displaystyle LRR = log_{10} \biggl(\frac{X_{i, I_1 \neq I_2, h_1 \neq h_2}}{X_{i, I_1 = I_2}}\biggr)\)
where \(X \in [N, P, C]\) is the trophic compartment of interest, \(i\) is either the local or meta-ecosystem, and \(I_1 \neq I_2\) and \(h_1 \neq h_2\) represent the relationships between environmental fertility conditions and primary producers recycling rates across local ecosystems, respectively.
A value of \(LRR = 0\) indicates that the ecosystem function values for scenario in question and those for the baseline scenario are perfectly matched. Values above 0 indicate that the ecosystem function values for the scenario of interest are larger than those of the baseline one. Vice versa, values of \(LRR < 0\) indicates that the baseline ecosystem function values are larger than those for the scenario of interest.
consmov_theme <- theme_pubr() + theme(legend.position = "bottom", legend.text = element_text(size = 12),
legend.title = element_text(size = 14), axis.title.x = element_text(size = 14,
face = "bold"), strip.text = element_text(size = 12))
How to interpret the LRR graphs below
In the \(LRR\) graphs below, the gray-shaded area below 0 indicates that the denominator of the ratio—i.e., the value of a given ecosystem function in the baseline scenario PRED—is larger than the numerator. In this case, consumers’ movement effectively reduces the ecosystem function of interest. Vice versa, the area above 0 indicates a larger numerator and an increase in the value of the ecosystem function of interest as a consequence consumers’ movement.
We are working with the PRED, PREDI2, and PREDI1 datasets we produced above (sections Model Analyses and Consumer movement and ecosystem functions), to produce Figure 2 in the Manuscript and Figure D1 in Online Appendix D.
First, we will check whether there is overlap among the equilibria that are either unstable or without biological meaning across PRED, PREDI2, and PREDI1. Then, we will join them into a new object, EnvFert_res.
Mode FALSE TRUE
logical 61 94
summary(PREDusns[["TIME"]] %in% PREDI2usns[["TIME"]])
Mode FALSE TRUE
logical 29 126
EnvFert_res <- bind_rows(Equal = PRED, Along = PREDI2, Against = PREDI1,
.id = "Fertility") %>% select(., biosense, Fertility:I2, N1:C2, Ntot:Ctot,
FLUX_P1:FLUX_C2, FLUX_Ptot:FLUX_Ctot, PROD_P1:PROD_Ctot)
There is not overlap among the three datasets in terms of equilibria that are unstable and lack biological meaning. This is something that will need accounting for later on when we work with \(LRR\).
For now, we can remove all equilibria that are either unstable or do not have biological sense as we are not going to create a figure using the raw data.
EnvFert_respos <- filter(EnvFert_res, biosense == "yes" | stable == "stable")
We now transform this new dataset to a long-format one, for ease of use in producing the plots. We also add two categorical variables: Function and Scale. Function captures the information of the ecosystem function being measured. Scale captures instead the information of whether the function is being measured at the local or meta-ecosystem scale.
EnvFert_long <- pivot_longer(EnvFert_respos, cols = N1:PROD_Ctot, names_to = "EcoFunction",
values_to = "Value") %>% dplyr::mutate(., Function = if_else(EcoFunction ==
"N1" | EcoFunction == "N2" | EcoFunction == "P1" | EcoFunction == "P2" |
EcoFunction == "C1" | EcoFunction == "C2" | EcoFunction == "Ntot" |
EcoFunction == "Ptot" | EcoFunction == "Ctot", "Stock", if_else(EcoFunction ==
"FLUX_P1" | EcoFunction == "FLUX_P2" | EcoFunction == "FLUX_C1" | EcoFunction ==
"FLUX_C2" | EcoFunction == "FLUX_Ptot" | EcoFunction == "FLUX_Ctot",
"Nutrient Flux", "Trophic Productivity"))) %>% dplyr::mutate(., Scale = if_else(EcoFunction ==
"N1" | EcoFunction == "P1" | EcoFunction == "C1" | EcoFunction == "FLUX_P1" |
EcoFunction == "FLUX_C1" | EcoFunction == "PROD_P1" | EcoFunction ==
"PROD_C1", "Ecosystem 1", if_else(EcoFunction == "N2" | EcoFunction ==
"P2" | EcoFunction == "C2" | EcoFunction == "FLUX_P2" | EcoFunction ==
"FLUX_C2" | EcoFunction == "PROD_P2" | EcoFunction == "PROD_C2", "Ecosystem 2",
"Meta-ecosystem"))) %>% dplyr::mutate(., Fertility = factor(Fertility),
Function = factor(Function), Scale = factor(Scale), EcoFunction = factor(EcoFunction))
head(EnvFert_long)
# A tibble: 6 x 9
biosense Fertility TIME I1 I2 EcoFunction Value Function
<fct> <fct> <dbl> <dbl> <dbl> <fct> <dbl> <fct>
1 yes Equal 1 10 10 N1 3.07 Stock
2 yes Equal 1 10 10 P1 1.43 Stock
3 yes Equal 1 10 10 C1 1.31 Stock
4 yes Equal 1 10 10 N2 3.26 Stock
5 yes Equal 1 10 10 P2 1.55 Stock
6 yes Equal 1 10 10 C2 2.68 Stock
# … with 1 more variable: Scale <fct>
Now that we have a dataset ready for plotting, we produce the graphs. The next code chunk produces Figure D.1 in Online Appendix D.
StockSumm <- EnvFert_long %>% filter(., Function == "Stock") %>% dplyr::mutate(.,
EcoFunction = fct_recode(EcoFunction, Nutrients = "N1", Nutrients = "N2",
Nutrients = "Ntot", `Primary\n Producers` = "P1", `Primary\n Producers` = "P2",
`Primary\n Producers` = "Ptot", Consumers = "C1", Consumers = "C2",
Consumers = "Ctot")) %>% dplyr::mutate(., EcoFunction = fct_relevel(EcoFunction,
"Nutrients", "Primary\n Producers", "Consumers")) %>% dplyr::mutate(.,
Fertility = fct_relevel(Fertility, "Equal", "Along", "Against")) %>%
ggplot(., aes(x = EcoFunction, y = Value)) + geom_hline(yintercept = 10,
linetype = "dashed", lwd = 0.25) + geom_boxplot(aes(color = Fertility)) +
scale_color_highcontrast(reverse = F, name = "Modelling scenario",
labels = c("Equal", "Along-gradient", "Against-gradient")) + facet_grid(. ~
Scale, scales = "free") + labs(title = "Nutrient Stock and Biomass") +
ylab(" ") + xlab(" ") + coord_cartesian(ylim = c(0, 75))
FluxSumm <- EnvFert_long %>% filter(., Function == "Nutrient Flux") %>%
dplyr::mutate(., EcoFunction = fct_recode(EcoFunction, `Primary\n Producers` = "FLUX_P1",
`Primary\n Producers` = "FLUX_P2", `Primary\n Producers` = "FLUX_Ptot",
Consumers = "FLUX_C1", Consumers = "FLUX_C2", Consumers = "FLUX_Ctot")) %>%
dplyr::mutate(., EcoFunction = fct_relevel(EcoFunction, "Primary\n Producers",
"Consumers")) %>% dplyr::mutate(., Fertility = fct_relevel(Fertility,
"Equal", "Along", "Against")) %>% ggplot(., aes(x = EcoFunction, y = Value)) +
geom_hline(yintercept = 50, linetype = "dashed", lwd = 0.25) + geom_boxplot(aes(color = Fertility)) +
scale_color_highcontrast(reverse = F, name = "Modelling scenario",
labels = c("Equal", "Along-gradient", "Against-gradient")) + facet_grid(. ~
Scale, scales = "free") + labs(title = "Nutrient Flux") + ylab(" ") +
xlab(" ") + coord_cartesian(ylim = c(0, 350))
ProdSumm <- EnvFert_long %>% filter(., Function == "Trophic Productivity") %>%
dplyr::mutate(., EcoFunction = fct_recode(EcoFunction, `Primary\n Producers` = "PROD_P1",
`Primary\n Producers` = "PROD_P2", `Primary\n Producers` = "PROD_Ptot",
Consumers = "PROD_C1", Consumers = "PROD_C2", Consumers = "PROD_Ctot")) %>%
dplyr::mutate(., EcoFunction = fct_relevel(EcoFunction, "Primary\n Producers",
"Consumers")) %>% dplyr::mutate(., Fertility = fct_relevel(Fertility,
"Equal", "Along", "Against")) %>% ggplot(., aes(x = EcoFunction, y = Value)) +
geom_hline(yintercept = 50, linetype = "dashed", lwd = 0.25) + geom_boxplot(aes(color = Fertility)) +
scale_color_highcontrast(reverse = F, name = "Modelling scenario",
labels = c("Equal", "Along-gradient", "Against-gradient")) + facet_grid(. ~
Scale, scales = "free") + labs(title = "Trophic Compartment Productivity") +
ylab(" ") + xlab("Compartment") + coord_cartesian(ylim = c(0, 500))
FuncAll <- StockSumm + FluxSumm + ProdSumm + plot_layout(ncol = 1, nrow = 3,
guides = "collect") + plot_annotation(tag_levels = "a", tag_prefix = "(",
tag_suffix = ")") & consmov_theme
FuncAll
ggsave(FuncAll, filename = "../Results/FunctionSummary_10kN.png", device = "png",
dpi = 600, width = 10, height = 10)
Now, we produce a LRR figure. First, we are going to create new dataframes (PREDI2rr and PREDI1rr1) that contain only the response ratio values for stock, nutrient flux, and production of all state variables. These dataframes will also contain information about the fertility conditions under which we measure each ecosystem function. We then merge them together in a single dataframe, EnvFertRR. As we are using PRED as the denominator of the response ratio, EnvFertRR will contain 20000 data points—the combined data points in PREDI1 and PREDI2.
WE will then remove the unstable, biologically not meaningful equilibria from PREDI1 and PREDI2 after calculating the response ratio and before creating EnvFertRR, so that the actual number of data points in it will be slightly less than 20000.
In this instance, the discrepancy in the number of equilibria that are either unstable or lack biological meaning among PRED, PREDI1, andPREDI2` matters.
To account for this, let us first create two objects, I2NsUsrm and I1NsUsrm that contain the unstable, nonsensical equilibria that are found in PRED but are not included in either PREDI1 and PREDI2. We are going to remove the ones in PREDI1 and PRED12 making use of the columns biosense and stable. Conversely, we will use I2NsUsrm and I1NsUsrm to remove instance of the response ratio being calculated using a denominator that is from an unstable, nonsensical equilibrium.
Then, let’s create two new objects, PREDI2rr and PREDI1rr, to store the response ratio values calculated by dividing ecosystem function values in PREDI2 and PREDI1 by those in PRED, respectively. We will then merge these two objects in a single one calle EnvFertRR
# need to keep raw data in the same df as the ratio to select the >0
# cases for state variables
PREDI2rr <- PREDI2 %>% # we are going to batch-create new response ratio versions of each
# variable we start with the 'stock' group of variables
dplyr::mutate(., N1rr = N1/PRED$N1, P1rr = P1/PRED$P1, C1rr = C1/PRED$C1,
N2rr = N2/PRED$N2, P2rr = P2/PRED$P2, C2rr = C2/PRED$C2, Nrr = Ntot/PRED$Ntot,
Prr = Ptot/PRED$Ptot, Crr = Ctot/PRED$Ctot) %>% # next, we work on the 'flux' group of variables
dplyr::mutate(., FLUX_P1rr = FLUX_P1/PRED$FLUX_P1, FLUX_C1rr = FLUX_C1/PRED$FLUX_C1,
FLUX_P2rr = FLUX_P2/PRED$FLUX_P2, FLUX_C2rr = FLUX_C2/PRED$FLUX_C2,
FLUX_Prr = FLUX_Ptot/PRED$FLUX_Ptot, FLUX_Crr = FLUX_Ctot/PRED$FLUX_Ctot) %>%
# finally, we work on the 'productivity' group of variables
dplyr::mutate(., PROD_P1rr = PROD_P1/PRED$PROD_P1, PROD_C1rr = PROD_C1/PRED$PROD_C1,
PROD_P2rr = PROD_P2/PRED$PROD_P2, PROD_C2rr = PROD_C2/PRED$PROD_C2,
PROD_Prr = PROD_Ptot/PRED$PROD_Ptot, PROD_Crr = PROD_Ctot/PRED$PROD_Ctot) %>%
# and now let's subset the dataframe to include the resp ratio columns
dplyr::select(., TIME:I2, N1:C2, N1rr:PROD_Crr, biosense, stable) %>% dplyr::filter(.,
biosense == "yes" | stable == "yes") %>% dplyr::filter(., !(TIME %in%
I2NsUsrm$TIME))
# repeat, but for PREDI1
PREDI1rr <- PREDI1 %>% # we are going to batch-create new response ratio versions of each
# variable we start with the 'stock' group of variables
dplyr::mutate(., N1rr = N1/PRED$N1, P1rr = P1/PRED$P1, C1rr = C1/PRED$C1,
N2rr = N2/PRED$N2, P2rr = P2/PRED$P2, C2rr = C2/PRED$C2, Nrr = Ntot/PRED$Ntot,
Prr = Ptot/PRED$Ptot, Crr = Ctot/PRED$Ctot) %>% # next, we work on the 'flux' group of variables
dplyr::mutate(., FLUX_P1rr = FLUX_P1/PRED$FLUX_P1, FLUX_C1rr = FLUX_C1/PRED$FLUX_C1,
FLUX_P2rr = FLUX_P2/PRED$FLUX_P2, FLUX_C2rr = FLUX_C2/PRED$FLUX_C2,
FLUX_Prr = FLUX_Ptot/PRED$FLUX_Ptot, FLUX_Crr = FLUX_Ctot/PRED$FLUX_Ctot) %>%
# finally, we work on the 'productivity' group of variables
dplyr::mutate(., PROD_P1rr = PROD_P1/PRED$PROD_P1, PROD_C1rr = PROD_C1/PRED$PROD_C1,
PROD_P2rr = PROD_P2/PRED$PROD_P2, PROD_C2rr = PROD_C2/PRED$PROD_C2,
PROD_Prr = PROD_Ptot/PRED$PROD_Ptot, PROD_Crr = PROD_Ctot/PRED$PROD_Ctot) %>%
# and now let's subset the dataframe to include only the resp ratio
# columns
dplyr::select(., TIME:I2, N1:C2, N1rr:PROD_Crr, biosense, stable) %>% dplyr::filter(.,
biosense == "yes" | stable == "yes") %>% dplyr::filter(., !(TIME %in%
I1NsUsrm$TIME))
EnvFertRR <- bind_rows(Along = PREDI2rr, Against = PREDI1rr, .id = "Fertility") %>%
dplyr::select(., Fertility, biosense, stable, TIME:I2, N1rr:PROD_Crr)
# now build the graph, first by pivoting to longer format, then
# graphing
Note that object EnvFertRR contains only 19301 as a result of two processes. First, the union of PREDI1 and PREDI2 (10000 rows each) the two rounds of removal of unstable of not biologically meaningful equilibria: the first round removes those unstable, biologically not meaningful equilibria contained in PREDI1 and PREDI2, the second uses objects INsUsrm and I2NsUsrm to remove the ones that are contained in PRED but do not overlap with those in PREDi1 or PREDI2. Now, we build the graphs. First, pivot the EnvFertRR dataframe from wide to long format.
EnvFertRR_long <- pivot_longer(EnvFertRR, cols = N1rr:PROD_Crr, names_to = "Compartment",
values_to = "Ratio") %>% dplyr::mutate(., Function = if_else(Compartment ==
"FLUX_P1rr" | Compartment == "FLUX_P2rr" | Compartment == "FLUX_C1rr" |
Compartment == "FLUX_C2rr" | Compartment == "FLUX_Prr" | Compartment ==
"FLUX_Crr", "Nutrient Flux", if_else(Compartment == "PROD_P1rr" | Compartment ==
"PROD_C1rr" | Compartment == "PROD_P2rr" | Compartment == "PROD_C2rr" |
Compartment == "PROD_Prr" | Compartment == "PROD_Crr", "Trophic Productivity",
"Stock"))) %>% dplyr::mutate(., Scale = if_else(Compartment == "N1rr" |
Compartment == "P1rr" | Compartment == "C1rr" | Compartment == "FLUX_P1rr" |
Compartment == "FLUX_C1rr" | Compartment == "PROD_P1rr" | Compartment ==
"PROD_C1rr", "Ecosystem 1", if_else(Compartment == "N2rr" | Compartment ==
"P2rr" | Compartment == "C2rr" | Compartment == "FLUX_P2rr" | Compartment ==
"FLUX_C2rr" | Compartment == "PROD_P2rr" | Compartment == "PROD_C2rr",
"Ecosystem 2", "Meta-ecosystem"))) %>% dplyr::mutate(., Fertility = factor(Fertility),
Function = factor(Function), Scale = factor(Scale), Compartment = factor(Compartment)) %>%
dplyr::mutate(., biosense = fct_drop(biosense), stable = fct_drop(stable))
In the following code chunk, we subset and then plot each ecosystem function individually against the \(LRR\), before putting them together with package patchwork to create Figure 2 in the Manuscript.
# FillFertpal <- c('#e8f8f9', '#F9E9E8')
ptHCsubset <- c("#DDAA33", "#BB5566")
EFstockLRR <- EnvFertRR_long %>% filter(., Function == "Stock") %>% dplyr::mutate(.,
Compartment = fct_recode(Compartment, Nutrients = "N1rr", Nutrients = "N2rr",
Nutrients = "Nrr", `Primary\n Producers` = "P1rr", `Primary\n Producers` = "P2rr",
`Primary\n Producers` = "Prr", Consumers = "C1rr", Consumers = "C2rr",
Consumers = "Crr")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Nutrients", "Primary\n Producers", "Consumers")) %>% dplyr::mutate(.,
Fertility = fct_relevel(Fertility, "Along", "Against")) %>% ggplot(.,
aes(x = Compartment, y = log10(Ratio))) + geom_rect(aes(xmin = -Inf,
xmax = Inf, ymin = -Inf, ymax = 0), fill = "gray95") + geom_hline(yintercept = 0,
linetype = "dashed", colour = "black") + geom_boxplot(aes(color = Fertility)) +
# geom_boxplot(aes(color = Fertility, fill = Fertility)) +
scale_color_manual(values = ptHCsubset, name = "Modelling scenario", labels = c("Along-gradient",
"Against-gradient")) + # scale_fill_manual(values = FillFertpal, labels = c('Higher in Donor',
# 'Higher in Recipient')) +
facet_grid(. ~ Scale, scales = "free") + labs(title = "Nutrient Stock and Biomass") +
ylab("Response Ratio (log10)") + xlab(" ")
EFfluxLRR <- EnvFertRR_long %>% filter(., Function == "Nutrient Flux") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "FLUX_P1rr",
`Primary\n Producers` = "FLUX_P2rr", `Primary\n Producers` = "FLUX_Prr",
Consumers = "FLUX_C1rr", Consumers = "FLUX_C2rr", Consumers = "FLUX_Crr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% dplyr::mutate(., Fertility = fct_relevel(Fertility,
"Along", "Against")) %>% ggplot(., aes(x = Compartment, y = log10(Ratio))) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0), fill = "gray95") +
geom_hline(yintercept = 0, linetype = "dashed", colour = "black") +
geom_boxplot(aes(color = Fertility)) + # geom_boxplot(aes(color = Fertility, fill = Fertility)) +
scale_color_manual(values = ptHCsubset, name = "Modelling scenario", labels = c("Along-gradient",
"Against-gradient")) + # scale_fill_manual(values = FillFertpal, labels = c('Higher in Donor',
# 'Higher in Recipient')) +
facet_grid(. ~ Scale, scales = "free") + labs(title = "Nutrient Flux") +
ylab("Response Ratio (log10)") + xlab(" ")
EFprodLRR <- EnvFertRR_long %>% filter(., Function == "Trophic Productivity") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "PROD_P1rr",
`Primary\n Producers` = "PROD_P2rr", `Primary\n Producers` = "PROD_Prr",
Consumers = "PROD_C1rr", Consumers = "PROD_C2rr", Consumers = "PROD_Crr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% dplyr::mutate(., Fertility = fct_relevel(Fertility,
"Along", "Against")) %>% ggplot(., aes(x = Compartment, y = log10(Ratio))) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0), fill = "gray95") +
geom_hline(yintercept = 0, linetype = "dashed", colour = "black") +
geom_boxplot(aes(color = Fertility)) + # geom_boxplot(aes(color = Fertility, fill = Fertility)) +
scale_color_manual(values = ptHCsubset, name = "Modelling scenario", labels = c("Along-gradient",
"Against-gradient")) + # scale_fill_manual(values = FillFertpal, labels = c('Higher in Donor',
# 'Higher in Recipient')) +
facet_grid(. ~ Scale, scales = "free") + labs(title = "Trophic Compartment Productivity") +
ylab("Response Ratio (log10)")
EFallLRR <- EFstockLRR + EFfluxLRR + EFprodLRR + plot_layout(ncol = 1,
nrow = 3, guides = "collect") + plot_annotation(tag_levels = "a", tag_prefix = "(",
tag_suffix = ")") & consmov_theme
EFallLRR
ggsave(EFallLRR, filename = "../Results/FunctionSummary_LRR_10kN.png",
device = "png", dpi = 600, width = 10, height = 10)
Here we are producing Figures 3 and 4 from the Manuscript, and well as Figures D.2 and D.3 from Online Appendix D. We will be working with several different sets of dataframes as, on top of the differences in environmental fertility conditions, we are also considering the differences in primary producers recycling rates. We will indicate which dataframes we are working with in each section below.
The procedure to generate the summary graphs is the same as above. Note that we focus on the case where \(\Delta h = 0.9\)—that is, the recycling rate of primary producers in one ecosystem is 90% larger than in the other ecosystem. In the graphs below, we will produce three boxplots for each trophic compartment: one for \(h_1 = h_2 = 0\) (blue), one for \(h_1 > h_2\) (green), and one for \(h_1 < h_2\) (red).
Here we work with the PREDI1recrates, PREDhigh_I1h1, and PREDhigh_I1h2 dataframes to create Figure 4 from the Manuscript and Figure D.3 from Online Appendix D. This set of simulation data captures the scenarios of high environmental fertility conditions in the donor ecosystem, and of higher recycling rates in the donor or recipient ecosystem.
First, we check whether the the unstable, biological nonsense equilibria in these three datasets overlap with those of our baseline scenario, PRED.
PREDI1eqh_usns <- filter(PREDI1recrates, biosense == "no" | stable == "unstable")
PREDhigh_I1h1usns <- filter(PREDhigh_I1h1, biosense == "no" | stable == "unstable")
PREDhigh_I1h2usns <- filter(PREDhigh_I1h2, biosense == "no" | stable == "unstable")
summary(PREDusns[["TIME"]] %in% PREDI1eqh_usns[["TIME"]])
Mode FALSE TRUE
logical 31 124
summary(PREDusns[["TIME"]] %in% PREDhigh_I1h1usns[["TIME"]])
Mode FALSE TRUE
logical 29 126
summary(PREDusns[["TIME"]] %in% PREDhigh_I1h2usns[["TIME"]])
Mode FALSE TRUE
logical 31 124
Not all of the unstable, biologically not meaningful equilibria from PRED show up in the three dataset we are working with here. Hence, when calculating the response ratio, we will do what we did before for EnvFertRR (see section Summary of Consumer movement and ecosystem functions above).
For now, as above, we can simply make use of variables biosense and stable in PREDI1recrates, PREDhigh_I1h1, and PREDhigh_I1h2 to remove the problematic equilibria. The code chunk below combines these dataframes into a new one called AlongEF.
# Combine the dataframes for the case of h1 > h2, irrespective of ∆I,
# then retain only certain columns, then create a new column for the
# fertility condition
AlongEF <- bind_rows(`Equal h` = PREDI1recrates, `High h1` = PREDhigh_I1h1,
`High h2` = PREDhigh_I1h2, .id = "PPRec") %>% dplyr::mutate(., PPRec = factor(PPRec)) %>%
group_by(., PPRec, hDIFF) %>% select(., biosense, stable, PPRec:I2,
N1:C2, Ntot:Ctot, FLUX_P1:FLUX_C2, FLUX_Ptot:FLUX_Ctot, PROD_P1:PROD_C2,
PROD_Ptot:PROD_Ctot)
Then, we filter out unstable, biologically not meaningful equilibria.
AlongEF_respos <- filter(AlongEF, biosense == "yes" | stable == "stable")
Now we produce the graphs for the raw data, starting by pivoting AlongEF to long format. We subset the resulting AlongEF_long dataframe to produce a graph each for stock, nutrient flux, and primary and secondary productivity. We then combine them using the patchwork package and produce Figure D.3 in Online Apendix D.
AlongEF_long <- AlongEF_respos %>% pivot_longer(., names_to = "Compartment",
values_to = "Value", cols = N1:PROD_Ctot) %>% # create Scale column, capturing local vs regional (=meta-eco) spatial
# extent
dplyr::mutate(., Scale = if_else(Compartment == "N1" | Compartment == "P1" |
Compartment == "C1" | Compartment == "FLUX_P1" | Compartment == "FLUX_C1" |
Compartment == "PROD_P1" | Compartment == "PROD_C1", "Ecosystem 1",
if_else(Compartment == "N2" | Compartment == "P2" | Compartment ==
"C2" | Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment ==
"PROD_P2" | Compartment == "PROD_C2", "Ecosystem 2", "Meta-ecosystem"))) %>%
# create Function column, capturing different ecosystem functions
# measured i.e., trophic compartment Stock, Nutrient Flux, Production
dplyr::mutate(., Function = if_else(Compartment == "FLUX_P1" | Compartment ==
"FLUX_P2" | Compartment == "FLUX_C1" | Compartment == "FLUX_C2" | Compartment ==
"FLUX_Ptot" | Compartment == "FLUX_Ctot", "Nutrient Flux", if_else(Compartment ==
"PROD_P1" | Compartment == "PROD_C1" | Compartment == "PROD_P2" | Compartment ==
"PROD_C2" | Compartment == "PROD_Ptot" | Compartment == "PROD_Ctot",
"Trophic Productivity", "Stock"))) %>% # transform columns of interest to factors
dplyr::mutate(., PPRec = factor(PPRec), hDIFF = factor(hDIFF), Scale = factor(Scale),
Compartment = factor(Compartment)) %>% # subset dataframe to include only no diff, 10% diff, and 90% diff in
# primary producers recycling rates (h_i) between local ecosystems
dplyr::filter(., hDIFF == "0" | hDIFF == "0.9") %>% # remove unused levels from hDIFF column
dplyr::mutate(., hDIFF = fct_drop(hDIFF), biosense = fct_drop(biosense),
stable = fct_drop(stable))
AlongEFStock <- AlongEF_long %>% dplyr::filter(., Function == "Stock") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, Nutrients = "N1",
Nutrients = "N2", Nutrients = "Ntot", `Primary\n Producers` = "P1",
`Primary\n Producers` = "P2", `Primary\n Producers` = "Ptot", Consumers = "C1",
Consumers = "C2", Consumers = "Ctot")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Nutrients", "Primary\n Producers", "Consumers")) %>% ggplot(., aes(x = Compartment,
y = Value)) + geom_hline(yintercept = 10, linetype = "dashed") + geom_boxplot(aes(col = PPRec)) +
facet_grid(. ~ Scale, scales = "free") + labs(y = " ", x = " ", color = "Primary Producers recycling rate",
title = "Nutrient Stock and Biomass") + # scale_color_hue(direction = -1, h.start = 90, labels = c('Equal',
# '90% higher in Donor', '90% higher in Recipient')) +
scale_color_highcontrast(reverse = F, labels = c("Equal", "Higher in Donor",
"Higher in Recipient")) + coord_cartesian(ylim = c(0, 100))
AlongEFFlux <- AlongEF_long %>% dplyr::filter(., Function == "Nutrient Flux") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "FLUX_P1",
`Primary\n Producers` = "FLUX_P2", `Primary\n Producers` = "FLUX_Ptot",
Consumers = "FLUX_C1", Consumers = "FLUX_C2", Consumers = "FLUX_Ctot")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% ggplot(., aes(x = Compartment, y = Value)) +
geom_hline(yintercept = 50, linetype = "dashed") + geom_boxplot(aes(col = PPRec)) +
facet_grid(. ~ Scale, scales = "free") + labs(y = " ", x = " ", color = "Primary Producers recycling rate",
title = "Nutrient Flux") + # scale_color_hue(direction = -1, h.start = 90, labels = c('Equal',
# '90% higher in Donor', '90% higher in Recipient')) +
scale_color_highcontrast(reverse = F, labels = c("Equal", "Higher in Donor",
"Higher in Recipient")) + coord_cartesian(ylim = c(0, 350))
AlongEFProd <- AlongEF_long %>% dplyr::filter(., Function == "Trophic Productivity") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "PROD_P1",
`Primary\n Producers` = "PROD_P2", `Primary\n Producers` = "PROD_Ptot",
Consumers = "PROD_C1", Consumers = "PROD_C2", Consumers = "PROD_Ctot")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% ggplot(., aes(x = Compartment, y = Value)) +
geom_hline(yintercept = 100, linetype = "dashed") + geom_boxplot(aes(col = PPRec)) +
facet_grid(. ~ Scale, scales = "free") + labs(y = " ", x = "Compartment",
color = "Primary Producers recycling rate", title = "Trophic Compartmet Productivity") +
# scale_color_hue(direction = -1, h.start = 90, labels = c('Equal',
# '90% higher in Donor', '90% higher in Recipient')) +
scale_color_highcontrast(reverse = F, labels = c("Equal", "Higher in Donor",
"Higher in Recipient")) + coord_cartesian(ylim = c(0, 500))
AlongEFall <- AlongEFStock + AlongEFFlux + AlongEFProd + plot_layout(ncol = 1,
nrow = 3, guides = "collect") + plot_annotation(tag_levels = "a", tag_prefix = "(",
tag_suffix = ")", title = "Consumer move along-gradient from high to low environmental fertility") &
consmov_theme
AlongEFall
ggsave(AlongEFall, filename = "../Results/FunctSumm_AlongEFraw_10kN.png",
device = "png", dpi = 600, width = 10, height = 10)
Now, let’s produce the log response ratio version of the same plot. We begin by calculating the log response ratio. This is accomplished by dividing every data column in the dataframe by the corresponding one in our baseline condition dataframe, i.e. PRED.
# now, group the resulting dataframe, then calculate the
# experiment:control ratio for each trophic compartment
AlongEFlrr <- AlongEF %>% # we are going to batch-create new response ratio versions of each
# variable we start with the 'stock' group of variables
dplyr::mutate(., N1rr = N1/PRED$N1, P1rr = P1/PRED$P1, C1rr = C1/PRED$C1,
N2rr = N2/PRED$N2, P2rr = P2/PRED$P2, C2rr = C2/PRED$C2, Nrr = Ntot/PRED$Ntot,
Prr = Ptot/PRED$Ptot, Crr = Ctot/PRED$Ctot) %>% # next, we work on the 'flux' group of variables
dplyr::mutate(., FLUX_P1rr = FLUX_P1/PRED$FLUX_P1, FLUX_C1rr = FLUX_C1/PRED$FLUX_C1,
FLUX_P2rr = FLUX_P2/PRED$FLUX_P2, FLUX_C2rr = FLUX_C2/PRED$FLUX_C2,
FLUX_Prr = FLUX_Ptot/PRED$FLUX_Ptot, FLUX_Crr = FLUX_Ctot/PRED$FLUX_Ctot) %>%
# finally, we work on the 'productivity' group of variables
dplyr::mutate(., PROD_P1rr = PROD_P1/PRED$PROD_P1, PROD_C1rr = PROD_C1/PRED$PROD_C1,
PROD_P2rr = PROD_P2/PRED$PROD_P2, PROD_C2rr = PROD_C2/PRED$PROD_C2,
PROD_Prr = PROD_Ptot/PRED$PROD_Ptot, PROD_Crr = PROD_Ctot/PRED$PROD_Ctot) %>%
dplyr::filter(., biosense == "yes" | stable == "stable")
Next, we need to remove those response ratios that use a denominator value that is part of either an unstable or biologically not meaningful equilibrium in PRED. As we did before, we create three objects to store non-overlapping values: one each for PRED non-overlapping values with PREDI1recrates, PREDhigh_I1h1, and PREDhigh_I1h2.
Now, we remove these non-overlapping values from the AlongEFlrr object created earlier.
AlongEFlrr <- dplyr::filter(AlongEFlrr, !(TIME %in% highI1_NsUsrm$TIME) |
!(TIME %in% highI1h1_NsUsrm$TIME) | !(TIME %in% highI1h2_NsUsrm$TIME))
Finally, now that we have the log response ratio for each ecosystem function, we subset AlongEFlrr to include only the ecosystem functions response ratio values and create a new dataframe, AlongEFlrr_long, with which we will be working to produce Figure 4 in the Manuscript.
# PPFillPal <- c('#d0e1fe', '#bae9c5', '#fcd6d4')
# now select only the metadata and ratios columns and pivot to a long
# dataframe, then create a Scale column
AlongEFlrr_long <- select(AlongEFlrr, biosense, stable, PPRec:I2, N1rr:PROD_Crr) %>%
# subset dataframe to include only no diff and 90% diff in primary
# producers recycling rates (h_i) between local ecosystems
dplyr::filter(., hDIFF == 0 | hDIFF == 0.9) %>% # pivot to longer format after selecting only columns of interest
pivot_longer(., names_to = "Compartment", values_to = "Ratio", cols = N1rr:PROD_Crr) %>%
# create Scale column, capturing local vs regional (=meta-eco) spatial
# extent
dplyr::mutate(., Scale = if_else(Compartment == "N1rr" | Compartment ==
"P1rr" | Compartment == "C1rr" | Compartment == "FLUX_P1rr" | Compartment ==
"FLUX_C1rr" | Compartment == "PROD_P1rr" | Compartment == "PROD_C1rr",
"Ecosystem 1", if_else(Compartment == "N2rr" | Compartment == "P2rr" |
Compartment == "C2rr" | Compartment == "FLUX_P2rr" | Compartment ==
"FLUX_C2rr" | Compartment == "PROD_P2rr" | Compartment == "PROD_C2rr",
"Ecosystem 2", "Meta-ecosystem"))) %>% # create Function column, capturing different ecosystem functions
# measured i.e., trophic compartment Stock, Nutrient Flux, Production
dplyr::mutate(., Function = if_else(Compartment == "FLUX_P1rr" | Compartment ==
"FLUX_P2rr" | Compartment == "FLUX_C1rr" | Compartment == "FLUX_C2rr" |
Compartment == "FLUX_Prr" | Compartment == "FLUX_Crr", "Nutrient Flux",
if_else(Compartment == "PROD_P1rr" | Compartment == "PROD_C1rr" | Compartment ==
"PROD_P2rr" | Compartment == "PROD_C2rr" | Compartment == "PROD_Prr" |
Compartment == "PROD_Crr", "Trophic Productivity", "Stock"))) %>%
# transform columns of interest to factors
dplyr::mutate(., PPRec = factor(PPRec), hDIFF = factor(hDIFF), Scale = factor(Scale),
Compartment = factor(Compartment), Function = factor(Function)) %>%
# remove unused levels from hDIFF, biosense, stable columns
dplyr::mutate(., hDIFF = fct_drop(hDIFF), biosense = fct_drop(biosense),
stable = fct_drop(stable))
# trophic compartment stock graph
AlongEFlrrStock <- AlongEFlrr_long %>% dplyr::filter(., Function == "Stock") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, Nutrients = "N1rr",
Nutrients = "N2rr", Nutrients = "Nrr", `Primary\n Producers` = "P1rr",
`Primary\n Producers` = "P2rr", `Primary\n Producers` = "Prr",
Consumers = "C1rr", Consumers = "C2rr", Consumers = "Crr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Nutrients",
"Primary\n Producers", "Consumers")) %>% ggplot(., aes(x = Compartment,
y = log10(Ratio))) + geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf,
ymax = 0), fill = "gray95") + geom_hline(yintercept = 0, linetype = "dashed",
color = "black") + geom_boxplot(aes(col = PPRec)) + facet_grid(. ~
Scale, scales = "free") + labs(y = "Response Ratio (log10)", color = "Primary Producers recycling rate",
title = "Nutrient Stocks and Biomass", x = " ") + scale_color_highcontrast(reverse = F,
labels = c("Equal", "Higher in Donor", "Higher in Recipient"))
# trophic compartment nutrient flux graph
AlongEFlrrFlux <- AlongEFlrr_long %>% dplyr::filter(., Function == "Nutrient Flux") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "FLUX_P1rr",
`Primary\n Producers` = "FLUX_P2rr", `Primary\n Producers` = "FLUX_Prr",
Consumers = "FLUX_C1rr", Consumers = "FLUX_C2rr", Consumers = "FLUX_Crr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% ggplot(., aes(x = Compartment, y = log10(Ratio))) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0), fill = "gray95") +
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
geom_boxplot(aes(col = PPRec)) + facet_grid(. ~ Scale, scales = "free") +
labs(y = "Response Ratio (log10)", color = "Primary Producers recycling rate",
title = "Nutrient Flux", x = " ") + scale_color_highcontrast(reverse = F,
labels = c("Equal", "Higher in Donor", "Higher in Recipient"))
# trophic compartment production graph
AlongEFlrrProd <- AlongEFlrr_long %>% dplyr::filter(., Function == "Trophic Productivity") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "PROD_P1rr",
`Primary\n Producers` = "PROD_P2rr", `Primary\n Producers` = "PROD_Prr",
Consumers = "PROD_C1rr", Consumers = "PROD_C2rr", Consumers = "PROD_Crr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% ggplot(., aes(x = Compartment, y = log10(Ratio))) +
geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = 0), fill = "gray95") +
geom_hline(yintercept = 0, linetype = "dashed", color = "black") +
geom_boxplot(aes(col = PPRec)) + facet_grid(. ~ Scale, scales = "free") +
labs(y = "Response Ratio (log10)", color = "Primary Producers recycling rate",
title = "Trophic Compartment Productivity") + scale_color_highcontrast(reverse = F,
labels = c("Equal", "Higher in Donor", "Higher in Recipient"))
AlongEFlrr_AllFunct <- AlongEFlrrStock + AlongEFlrrFlux + AlongEFlrrProd +
plot_layout(ncol = 1, nrow = 3, guides = "collect") + plot_annotation(title = "Consumer move along-gradient from high to low environmental fertility",
tag_levels = "a", tag_prefix = "(", tag_suffix = ")") & consmov_theme
AlongEFlrr_AllFunct
Figure 39: .
ggsave(AlongEFlrr_AllFunct, filename = "../Results/FunctSumm_AlongEFlrr_10kN.png",
dpi = 600, device = "png", width = 10, height = 10)
Here we work with the PREDI2recrates, PREDhigh_I2h1, and PREDhigh_I2h2 dataframes. Following the same workflow, we now produce raw data and log response ratio graphs for the case of against-gradient movement, shown as Figure D. in Online Appendix D and Figure 3 in the Manuscript, respectively.
First, we check whether the the unstable, biological nonsense equilibria in these three datasets overlap with those of our baseline scenario, PRED.
PREDI2eqh_usns <- filter(PREDI2recrates, biosense == "no" | stable == "unstable")
PREDhigh_I2h1usns <- filter(PREDhigh_I2h1, biosense == "no" | stable == "unstable")
PREDhigh_I2h2usns <- filter(PREDhigh_I2h2, biosense == "no" | stable == "unstable")
summary(PREDusns[["TIME"]] %in% PREDI2eqh_usns[["TIME"]])
Mode FALSE TRUE
logical 67 88
summary(PREDusns[["TIME"]] %in% PREDhigh_I2h1usns[["TIME"]])
Mode FALSE TRUE
logical 67 88
summary(PREDusns[["TIME"]] %in% PREDhigh_I2h2usns[["TIME"]])
Mode FALSE TRUE
logical 67 88
As it happened above in the Along gradient movement section, not all of the unstable and biologically not meaningful equilibria from PRED show up in the three dataset we are working with here. Hence, when calculating the response ratio below we will follow the same steps we did in the earlier sections to make sure to remove all of these problematic equilibria before producing the graphs.
We begin by combining the relevant dataframes into a single object, AgainstEF.
# Combine the dataframes for the case of h1 > h2, irrespective of ∆I,
# then retain only certain columns, then create a new column for the
# fertility condition
AgainstEF <- bind_rows(`Equal h` = PREDI2recrates, `High h1` = PREDhigh_I2h1,
`High h2` = PREDhigh_I2h2, .id = "PPRec") %>% select(., biosense, stable,
PPRec:I2, N1:C2, Ntot:Ctot, FLUX_P1:FLUX_C2, FLUX_Ptot:FLUX_Ctot, PROD_P1:PROD_C2,
PROD_Ptot:PROD_Ctot) %>% dplyr::mutate_if(is.factor, as.character)
Let’s filter out unstable, biologically not meaningful equilibria using the biosense and stable variables. We can do this here because the first figure we produce uses only raw data points from PREDI2recrates, PREDhigh_I2h1, and PREDhigh_I2h2.
The code chunk below lengthens AgainstEF into AgainstEF_long and then uses this new dataframe to produce the graphs. For each ecosystem function of interest, we subset AgainstEF_long and build Figure D.2 in Online Appendix D from the resulting selection of values.
AgainstEF_long <- AgainstEF_respos %>% # subset dataframe to include only no diff and 90% diff in primary
# producers recycling rates (h_i) between local ecosystems
dplyr::filter(., hDIFF == "0" | hDIFF == "0.9") %>% pivot_longer(., names_to = "Compartment",
values_to = "Value", cols = N1:PROD_Ctot) %>% # create Scale column, capturing local vs regional (=meta-eco) spatial
# extent
dplyr::mutate(., Scale = if_else(Compartment == "N1" | Compartment == "P1" |
Compartment == "C1" | Compartment == "FLUX_P1" | Compartment == "FLUX_C1" |
Compartment == "PROD_P1" | Compartment == "PROD_C1", "Ecosystem 1",
if_else(Compartment == "N2" | Compartment == "P2" | Compartment ==
"C2" | Compartment == "FLUX_P2" | Compartment == "FLUX_C2" | Compartment ==
"PROD_P2" | Compartment == "PROD_C2", "Ecosystem 2", "Meta-ecosystem"))) %>%
# create Function column, capturing different ecosystem functions
# measured i.e., trophic compartment Stock, Nutrient Flux, Production
dplyr::mutate(., Function = if_else(Compartment == "FLUX_P1" | Compartment ==
"FLUX_P2" | Compartment == "FLUX_C1" | Compartment == "FLUX_C2" | Compartment ==
"FLUX_Ptot" | Compartment == "FLUX_Ctot", "Nutrient Flux", if_else(Compartment ==
"PROD_P1" | Compartment == "PROD_C1" | Compartment == "PROD_P2" | Compartment ==
"PROD_C2" | Compartment == "PROD_Ptot" | Compartment == "PROD_Ctot",
"Trophic Productivity", "Stock"))) %>% # transform columns of interest to factors
dplyr::mutate(., PPRec = factor(PPRec), hDIFF = factor(hDIFF), Scale = factor(Scale),
Compartment = factor(Compartment)) %>% # remove unused levels from hDIFF column
dplyr::mutate(., hDIFF = fct_drop(hDIFF), biosense = fct_drop(biosense),
stable = fct_drop(stable))
AgainstEFStock <- AgainstEF_long %>% dplyr::filter(., Function == "Stock") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, Nutrients = "N1",
Nutrients = "N2", Nutrients = "Ntot", `Primary\n Producers` = "P1",
`Primary\n Producers` = "P2", `Primary\n Producers` = "Ptot", Consumers = "C1",
Consumers = "C2", Consumers = "Ctot")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Nutrients", "Primary\n Producers", "Consumers")) %>% ggplot(., aes(x = Compartment,
y = Value)) + geom_hline(yintercept = 10, linetype = "dashed") + geom_boxplot(aes(col = PPRec)) +
facet_grid(. ~ Scale, scales = "free") + labs(y = " ", x = " ", color = "Primary Producers recycling rate",
title = "Nutrient Stock and Biomass") + scale_color_highcontrast(reverse = F,
labels = c("Equal", "Higher in Donor", "Higher in Recipient")) + coord_cartesian(ylim = c(0,
100))
AgainstEFFlux <- AgainstEF_long %>% dplyr::filter(., Function == "Nutrient Flux") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "FLUX_P1",
`Primary\n Producers` = "FLUX_P2", `Primary\n Producers` = "FLUX_Ptot",
Consumers = "FLUX_C1", Consumers = "FLUX_C2", Consumers = "FLUX_Ctot")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% ggplot(., aes(x = Compartment, y = Value)) +
geom_hline(yintercept = 50, linetype = "dashed") + geom_boxplot(aes(col = PPRec)) +
facet_grid(. ~ Scale, scales = "free") + labs(y = " ", x = " ", color = "Primary Producers recycling rate",
title = "Nutrient Flux") + scale_color_highcontrast(reverse = F, labels = c("Equal",
"Higher in Donor", "Higher in Recipient")) + coord_cartesian(ylim = c(0,
500))
AgainstEFProd <- AgainstEF_long %>% dplyr::filter(., Function == "Trophic Productivity") %>%
dplyr::mutate(., Compartment = fct_recode(Compartment, `Primary\n Producers` = "PROD_P1",
`Primary\n Producers` = "PROD_P2", `Primary\n Producers` = "PROD_Ptot",
Consumers = "PROD_C1", Consumers = "PROD_C2", Consumers = "PROD_Ctot")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Primary\n Producers",
"Consumers")) %>% ggplot(., aes(x = Compartment, y = Value)) +
geom_hline(yintercept = 100, linetype = "dashed") + geom_boxplot(aes(col = PPRec)) +
facet_grid(. ~ Scale, scales = "free") + labs(y = " ", x = "Compartment",
color = "Primary Producers recycling rate", title = "Trophic Compartment Productivity") +
scale_color_highcontrast(reverse = F, labels = c("Equal", "Higher in Donor",
"Higher in Recipient")) + coord_cartesian(ylim = c(0, 700))
AgainstEFall <- AgainstEFStock + AgainstEFFlux + AgainstEFProd + plot_layout(ncol = 1,
nrow = 3, guides = "collect") + plot_annotation(tag_levels = "a", tag_prefix = "(",
tag_suffix = ")", title = "Consumer move against-gradient from low to high environmental fertility") &
consmov_theme
AgainstEFall
ggsave(AgainstEFall, filename = "../Results/FunctSumm_AgainstEFraw_10kN.png",
device = "png", dpi = 600, width = 10, height = 10)
Now, we move on to producing the log response ratio version of the graphs we just produced above. We first calculate the log response ratio for each ecosystem function by dividing the relevant data columns in AgainstEFlrr by the corresponding ones in the baseline condition dataframe, i.e., PRED.
# now, group the resulting dataframe, then calculate the
# experiment:control ratio for each trophic compartment
AgainstEFlrr <- AgainstEF %>% # we are going to batch-create new response ratio versions of each
# variable we start with the 'stock' group of variables
dplyr::mutate(., N1rr = N1/PRED$N1, P1rr = P1/PRED$P1, C1rr = C1/PRED$C1,
N2rr = N2/PRED$N2, P2rr = P2/PRED$P2, C2rr = C2/PRED$C2, Nrr = Ntot/PRED$Ntot,
Prr = Ptot/PRED$Ptot, Crr = Ctot/PRED$Ctot) %>% # next, we work on the 'flux' group of variables
dplyr::mutate(., FLUX_P1rr = FLUX_P1/PRED$FLUX_P1, FLUX_C1rr = FLUX_C1/PRED$FLUX_C1,
FLUX_P2rr = FLUX_P2/PRED$FLUX_P2, FLUX_C2rr = FLUX_C2/PRED$FLUX_C2,
FLUX_Prr = FLUX_Ptot/PRED$FLUX_Ptot, FLUX_Crr = FLUX_Ctot/PRED$FLUX_Ctot) %>%
# finally, we work on the 'productivity' group of variables
dplyr::mutate(., PROD_P1rr = PROD_P1/PRED$PROD_P1, PROD_C1rr = PROD_C1/PRED$PROD_C1,
PROD_P2rr = PROD_P2/PRED$PROD_P2, PROD_C2rr = PROD_C2/PRED$PROD_C2,
PROD_Prr = PROD_Ptot/PRED$PROD_Ptot, PROD_Crr = PROD_Ctot/PRED$PROD_Ctot) %>%
dplyr::filter(., biosense == "yes" | stable == "stable") %>% dplyr::mutate(.,
biosense = factor(biosense), stable = factor(stable), PPRec = factor(PPRec),
hDIFF = factor(hDIFF)) %>% dplyr::group_by(., PPRec, hDIFF)
Next, we remove those response ratios that use a denominator value that is part of either an unstable or biologically not meaningful equilibrium in PRED. As we did before, we create three objects to store non-overlapping values: one each for PRED non-overlapping values with PREDI2recrates, PREDhigh_I2h1, and PREDhigh_I2h2.
Now, we remove these non-overlapping values from the AlongEFlrr object created earlier.
AgainstEFlrr <- dplyr::filter(AgainstEFlrr, !(TIME %in% highI2_NsUsrm$TIME) |
!(TIME %in% highI2h1_NsUsrm$TIME) | !(TIME %in% highI2h2_NsUsrm$TIME))
With the log response ratio values in the dataframe, we now proceed to separate them from the raw data into a new dataframe, AgainstEFlrr_long, and pivoting it to long format. We then use this new object to produce Figure 3 in the Manuscript.
# now select only the metadata and ratios columns and pivot to a long
# dataframe, then create a Scale column
AgainstEFlrr_long <- select(AgainstEFlrr, biosense, stable, PPRec:I2, N1rr:PROD_Crr) %>%
# subset dataframe to include only no diff and 90% diff in primary
# producers recycling rates (h_i) between local ecosystems
dplyr::filter(., hDIFF == "0" | hDIFF == "0.9") %>% # pivot to longer format after selecting only columns of interest
pivot_longer(., names_to = "Compartment", values_to = "Ratio", cols = N1rr:PROD_Crr) %>%
# create Scale column, capturing local vs regional (=meta-eco) spatial
# extent
dplyr::mutate(., Scale = if_else(Compartment == "N1rr" | Compartment ==
"P1rr" | Compartment == "C1rr" | Compartment == "FLUX_P1rr" | Compartment ==
"FLUX_C1rr" | Compartment == "PROD_P1rr" | Compartment == "PROD_C1rr",
"Ecosystem 1", if_else(Compartment == "N2rr" | Compartment == "P2rr" |
Compartment == "C2rr" | Compartment == "FLUX_P2rr" | Compartment ==
"FLUX_C2rr" | Compartment == "PROD_P2rr" | Compartment == "PROD_C2rr",
"Ecosystem 2", "Meta-ecosystem"))) %>% # create Function column, capturing different ecosystem functions
# measured i.e., trophic compartment Stock, Nutrient Flux, Production
dplyr::mutate(., Function = if_else(Compartment == "FLUX_P1rr" | Compartment ==
"FLUX_P2rr" | Compartment == "FLUX_C1rr" | Compartment == "FLUX_C2rr" |
Compartment == "FLUX_Prr" | Compartment == "FLUX_Crr", "Nutrient Flux",
if_else(Compartment == "PROD_P1rr" | Compartment == "PROD_C1rr" | Compartment ==
"PROD_P2rr" | Compartment == "PROD_C2rr" | Compartment == "PROD_Prr" |
Compartment == "PROD_Crr", "Trophic Productivity", "Stock"))) %>%
# transform columns of interest to factors
dplyr::mutate(., Scale = factor(Scale), Compartment = factor(Compartment)) %>%
# remove unused levels from hDIFF column
dplyr::mutate(., hDIFF = fct_drop(hDIFF))
# trophic compartment stock graph
AgainstEFlrrStock <- AgainstEFlrr_long %>% dplyr::filter(., Function ==
"Stock") %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Nutrients = "N1rr", Nutrients = "N2rr", Nutrients = "Nrr", `Primary\n Producers` = "P1rr",
`Primary\n Producers` = "P2rr", `Primary\n Producers` = "Prr", Consumers = "C1rr",
Consumers = "C2rr", Consumers = "Crr")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Nutrients", "Primary\n Producers", "Consumers")) %>% ggplot(., aes(x = Compartment,
y = log10(Ratio))) + geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf,
ymax = 0), fill = "gray95") + geom_hline(yintercept = 0, linetype = "dashed",
color = "black") + geom_boxplot(aes(col = PPRec)) + facet_grid(. ~
Scale, scales = "free") + labs(y = "Respose Ratio (log10)", color = "Primary Producers recycling rate",
title = "Nutrient Stock and Biomass", x = " ") + scale_color_highcontrast(reverse = F,
labels = c("Equal", "90% higher in Donor", "90% higher in Recipient"))
# trophic compartment nutrient flux graph
AgainstEFlrrFlux <- AgainstEFlrr_long %>% dplyr::filter(., Function ==
"Nutrient Flux") %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
`Primary\n Producers` = "FLUX_P1rr", `Primary\n Producers` = "FLUX_P2rr",
`Primary\n Producers` = "FLUX_Prr", Consumers = "FLUX_C1rr", Consumers = "FLUX_C2rr",
Consumers = "FLUX_Crr")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Primary\n Producers", "Consumers")) %>% ggplot(., aes(x = Compartment,
y = log10(Ratio))) + geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf,
ymax = 0), fill = "gray95") + geom_hline(yintercept = 0, linetype = "dashed",
colour = "black") + geom_boxplot(aes(col = PPRec)) + facet_grid(. ~
Scale, scales = "free") + labs(y = "Response Ratio (log10)", color = "Primary Producers recycling rate",
title = "Nutrient Flux", x = " ") + scale_color_highcontrast(reverse = F,
labels = c("Equal", "90% higher in Donor", "90% higher in Recipient"))
# trophic compartment production graph
AgainstEFlrrProd <- AgainstEFlrr_long %>% dplyr::filter(., Function ==
"Trophic Productivity") %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
`Primary\n Producers` = "PROD_P1rr", `Primary\n Producers` = "PROD_P2rr",
`Primary\n Producers` = "PROD_Prr", Consumers = "PROD_C1rr", Consumers = "PROD_C2rr",
Consumers = "PROD_Crr")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Primary\n Producers", "Consumers")) %>% ggplot(., aes(x = Compartment,
y = log10(Ratio))) + geom_rect(aes(xmin = -Inf, xmax = Inf, ymin = -Inf,
ymax = 0), fill = "gray95") + geom_hline(yintercept = 0, linetype = "dashed",
colour = "black") + geom_boxplot(aes(col = PPRec)) + facet_grid(. ~
Scale, scales = "free") + labs(y = "Response Ratio (log10)", color = "Primary Producers recycling rate",
title = "Trophic Compartment Productivity") + scale_color_highcontrast(revers = F,
labels = c("Equal", "90% higher in Donor", "90% higher in Recipient"))
AgainstEFlrr_AllFunct <- AgainstEFlrrStock + AgainstEFlrrFlux + AgainstEFlrrProd +
plot_layout(ncol = 1, nrow = 3, guides = "collect") + plot_annotation(title = "Consumer move against-gradient from low to high environmental fertility",
tag_levels = "a", tag_prefix = "(", tag_suffix = ")") & consmov_theme
AgainstEFlrr_AllFunct
Figure 40: .
ggsave(AgainstEFlrr_AllFunct, filename = "../Results/FunctSumm_AgainstEFlrr_10kN.png",
dpi = 600, device = "png", width = 10, height = 10)
Here, we produce summary tables for the simulations above using the \(LRR\) values we calculated to produce the summary plots above. In these tables, we report median \(LRR\) values for each ecosystem function of interest in both local ecosystems and in the meta-ecosystem. These tables further summarize information presented in the graphs above, by reducing the whole variation to a single value. As above, we focus only on the case of \(\Delta h = 0.9\).
For ease of interpretation, median \(LRR\) values < 0 will be shown in shades of red based on how close they are to -1 and median LRR > 0 will be shown in shades of green based on their closeness to 1.
First, we produce dataframes to contain the median values, one for each of stock, nutrient flux, and primary and secondary productivity.
# first, compute median of log10 ratio values
EnvFertLRR_tables <- dplyr::filter(EnvFertRR_long, Ratio >= 0) %>% dplyr::group_by(.,
Fertility, Function, Scale, Compartment) %>% dplyr::mutate(., LRR = log10(Ratio)) %>%
dplyr::summarise(., min = min(LRR), median = median(LRR), max = max(LRR),
.groups = "keep")
AlongEFlrr_tables <- dplyr::filter(AlongEFlrr_long, Ratio >= 0) %>% dplyr::group_by(.,
PPRec, Function, Scale, Compartment) %>% dplyr::mutate(., LRR = log10(Ratio)) %>%
dplyr::summarise(., min = min(LRR), median = median(LRR), max = max(LRR),
.groups = "keep")
AgainstEFlrr_tables <- dplyr::filter(AgainstEFlrr_long, Ratio >= 0) %>%
dplyr::group_by(., PPRec, Function, Scale, Compartment) %>% dplyr::mutate(.,
LRR = log10(Ratio)) %>% dplyr::summarise(., min = min(LRR), median = median(LRR),
max = max(LRR), .groups = "keep")
Now, we produce the tables, separating between along and against-gradient movement, as above.
EFStockTab <- EnvFertLRR_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Stock") %>% pivot_wider(., names_from = Fertility, values_from = c(min,
median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "C1rr", Consumers = "C2rr", Consumers = "Crr", Producers = "P1rr",
Producers = "P2rr", Producers = "Prr", Nutrients = "N1rr", Nutrients = "N2rr",
Nutrients = "Nrr")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Nutrients", "Producers", "Consumers")) %>% gt(groupname_col = "Scale") %>%
cols_hide("Function") %>% tab_spanner(label = md("**Along-gradient**"),
columns = c("min_Along", "median_Along", "max_Along")) %>% tab_spanner(label = md("**Against-gradient**"),
columns = c("min_Against", "median_Against", "max_Against")) %>% cols_label(Compartment = md("**Compartment**"),
min_Along = md("*Min.*"), median_Along = md("*Median*"), max_Along = md("*Max.*"),
min_Against = md("*Min.*"), median_Against = md("*Median*"), max_Against = md("*Max.*")) %>%
fmt_number(., columns = c(4:9), decimals = 2) %>% data_color(., columns = c(6,
7), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in nutrient stock and organic biomass accumulation in the meta-ecosystem when consumers move against or along an environmental fertility gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(EFStockTab, filename = "EFbiostock.tex", path = "../Results/")
EFStockTab
| Change in nutrient stock and organic biomass accumulation in the meta-ecosystem when consumers move against or along an environmental fertility gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | ||||||
|---|---|---|---|---|---|---|
| Compartment | Against-gradient | Along-gradient | ||||
| Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | ||||||
| Consumers | −3.16 | −0.72 | −0.70 | 0.26 | 0.26 | 2.45 |
| Nutrients | −0.70 | −0.16 | 0.00 | 0.00 | 0.12 | 0.26 |
| Producers | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| Ecosystem 2 | ||||||
| Consumers | −0.80 | 0.17 | 0.92 | −1.35 | −0.27 | 0.31 |
| Nutrients | −0.68 | 0.11 | 0.26 | −0.70 | −0.14 | 0.25 |
| Producers | 0.00 | 0.04 | 1.46 | −2.72 | −0.12 | 0.00 |
| Meta-ecosystem | ||||||
| Consumers | −1.16 | 0.08 | 0.59 | −1.08 | −0.09 | 0.57 |
| Nutrients | −0.67 | 0.03 | 0.25 | −0.68 | −0.03 | 0.25 |
| Producers | 0.00 | 0.01 | 0.90 | −0.80 | −0.02 | 0.00 |
EFFluxTab <- EnvFertLRR_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Nutrient Flux") %>% pivot_wider(., names_from = Fertility,
values_from = c(min, median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "FLUX_C1rr", Consumers = "FLUX_C2rr", Consumers = "FLUX_Crr",
Producers = "FLUX_P1rr", Producers = "FLUX_P2rr", Producers = "FLUX_Prr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Producers",
"Consumers")) %>% gt(groupname_col = "Scale") %>% cols_hide("Function") %>%
tab_spanner(label = md("**Along-gradient**"), columns = c("min_Along",
"median_Along", "max_Along")) %>% tab_spanner(label = md("**Against-gradient**"),
columns = c("min_Against", "median_Against", "max_Against")) %>% cols_label(Compartment = md("**Compartment**"),
min_Along = md("*Min.*"), median_Along = md("*Median*"), max_Along = md("*Max.*"),
min_Against = md("*Min.*"), median_Against = md("*Median*"), max_Against = md("*Max.*")) %>%
fmt_number(., columns = c(4:9), decimals = 2) %>% data_color(., columns = c(6,
7), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in nutrient flux in the meta-ecosystem when consumers move against or along an environmental fertility gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(EFFluxTab, filename = "EFnutflux.tex", path = "../Results/")
EFFluxTab
| Change in nutrient flux in the meta-ecosystem when consumers move against or along an environmental fertility gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | ||||||
|---|---|---|---|---|---|---|
| Compartment | Against-gradient | Along-gradient | ||||
| Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | ||||||
| Consumers | −3.16 | −0.72 | −0.70 | 0.26 | 0.26 | 2.45 |
| Producers | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| Ecosystem 2 | ||||||
| Consumers | −0.80 | 0.17 | 0.92 | −1.35 | −0.27 | 0.31 |
| Producers | 0.00 | 0.04 | 1.46 | −2.72 | −0.12 | 0.00 |
| Meta-ecosystem | ||||||
| Consumers | −0.86 | 0.07 | 0.49 | −1.06 | −0.09 | 0.57 |
| Producers | 0.00 | 0.01 | 0.90 | −0.86 | −0.02 | 0.00 |
EFProdTab <- EnvFertLRR_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Trophic Productivity") %>% pivot_wider(., names_from = Fertility,
values_from = c(min, median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "PROD_C1rr", Consumers = "PROD_C2rr", Consumers = "PROD_Crr",
Producers = "PROD_P1rr", Producers = "PROD_P2rr", Producers = "PROD_Prr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Producers",
"Consumers")) %>% gt(groupname_col = "Scale") %>% cols_hide("Function") %>%
tab_spanner(label = md("**Along-gradient**"), columns = c("min_Along",
"median_Along", "max_Along")) %>% tab_spanner(label = md("**Against-gradient**"),
columns = c("min_Against", "median_Against", "max_Against")) %>% cols_label(Compartment = md("**Compartment**"),
min_Along = md("*Min.*"), median_Along = md("*Median*"), max_Along = md("*Max.*"),
min_Against = md("*Min.*"), median_Against = md("*Median*"), max_Against = md("*Max.*")) %>%
fmt_number(., columns = c(4:9), decimals = 2) %>% data_color(., columns = c(6,
7), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in trophic compartment productivity in the meta-ecosystem when consumers move against or along an environmental fertility gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(EFProdTab, filename = "EFprod.tex", path = "../Results/")
EFProdTab
| Change in trophic compartment productivity in the meta-ecosystem when consumers move against or along an environmental fertility gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | ||||||
|---|---|---|---|---|---|---|
| Compartment | Against-gradient | Along-gradient | ||||
| Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | ||||||
| Consumers | −3.16 | −0.72 | −0.70 | 0.26 | 0.26 | 2.45 |
| Producers | −0.70 | −0.16 | 0.00 | 0.00 | 0.12 | 0.26 |
| Ecosystem 2 | ||||||
| Consumers | 0.01 | 0.22 | 1.40 | −2.63 | −0.47 | −0.01 |
| Producers | 0.00 | 0.18 | 1.40 | −2.63 | −0.34 | 0.00 |
| Meta-ecosystem | ||||||
| Consumers | −0.88 | 0.03 | 0.47 | −1.02 | −0.04 | 0.70 |
| Producers | −0.47 | 0.03 | 0.88 | −0.81 | −0.04 | 0.22 |
Note: column headers refer to the direction of the primary producers recycling rate gradient. Equal, equal primary producers recycling rates; High in Donor, primary producers recycling rates higher in ecosystem 1; High in Recipient, primary producers recycling rates higher in ecosystem 2
AlongEFStockTab <- AlongEFlrr_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Stock") %>% pivot_wider(., names_from = PPRec, values_from = c(min,
median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "C1rr", Consumers = "C2rr", Consumers = "Crr", Producers = "P1rr",
Producers = "P2rr", Producers = "Prr", Nutrients = "N1rr", Nutrients = "N2rr",
Nutrients = "Nrr")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Nutrients", "Producers", "Consumers")) %>% gt(groupname_col = "Scale") %>%
cols_hide("Function") %>% tab_spanner(label = md("**Equal**"), columns = c("min_Equal h",
"median_Equal h", "max_Equal h")) %>% tab_spanner(label = md("**High in Donor**"),
columns = c("min_High h1", "median_High h1", "max_High h1")) %>% tab_spanner(label = md("**High in Recipient**"),
columns = c("min_High h2", "median_High h2", "max_High h2")) %>% cols_label(Compartment = md("**Compartment**"),
`min_Equal h` = md("*Min.*"), `median_Equal h` = md("*Median*"), `max_Equal h` = md("*Max.*"),
`min_High h1` = md("*Min.*"), `median_High h1` = md("*Median*"), `max_High h1` = md("*Max.*"),
`min_High h2` = md("*Min.*"), `median_High h2` = md("*Median*"), `max_High h2` = md("*Max.*")) %>%
fmt_number(., columns = c(4:12), decimals = 2) %>% data_color(., columns = c(7,
8, 9), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in nutrient stock and organic biomass accumulation in the meta-ecosystem when consumers move along an environmental fertility gradient, in the presence of a primary producers recycling rates gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(AlongEFStockTab, filename = "AlongEFbiostock.tex", path = "../Results/")
AlongEFStockTab
| Change in nutrient stock and organic biomass accumulation in the meta-ecosystem when consumers move along an environmental fertility gradient, in the presence of a primary producers recycling rates gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| Compartment | Equal | High in Donor | High in Recipient | ||||||
| Min. | Median | Max. | Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | |||||||||
| Consumers | 0.26 | 0.26 | 2.45 | −0.91 | 0.26 | 0.26 | 0.26 | 0.26 | 2.77 |
| Nutrients | 0.00 | 0.12 | 0.26 | 0.26 | 0.27 | 0.28 | −1.00 | −0.12 | 0.26 |
| Producers | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| Ecosystem 2 | |||||||||
| Consumers | −1.93 | −0.27 | 0.51 | −1.93 | −0.28 | 0.51 | −1.93 | −0.27 | 0.71 |
| Nutrients | −1.66 | −0.18 | 1.57 | −1.66 | −0.18 | 1.56 | −1.66 | −0.18 | 1.57 |
| Producers | −3.15 | −0.12 | 0.10 | −3.54 | −0.12 | 0.10 | −2.96 | −0.12 | 0.10 |
| Meta-ecosystem | |||||||||
| Consumers | −1.60 | −0.09 | 0.65 | −1.60 | −0.10 | 0.33 | −1.60 | −0.09 | 0.96 |
| Nutrients | −1.07 | −0.04 | 0.60 | −0.98 | 0.03 | 0.63 | −1.24 | −0.16 | 0.57 |
| Producers | −0.80 | −0.02 | 0.10 | −0.80 | −0.02 | 0.10 | −0.90 | −0.02 | 0.25 |
AlongEFFluxTab <- AlongEFlrr_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Nutrient Flux") %>% pivot_wider(., names_from = PPRec,
values_from = c(min, median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "FLUX_C1rr", Consumers = "FLUX_C2rr", Consumers = "FLUX_Crr",
Producers = "FLUX_P1rr", Producers = "FLUX_P2rr", Producers = "FLUX_Prr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Producers",
"Consumers")) %>% gt(groupname_col = "Scale") %>% cols_hide("Function") %>%
tab_spanner(label = md("**Equal**"), columns = c("min_Equal h", "median_Equal h",
"max_Equal h")) %>% tab_spanner(label = md("**High in Donor**"),
columns = c("min_High h1", "median_High h1", "max_High h1")) %>% tab_spanner(label = md("**High in Recipient**"),
columns = c("min_High h2", "median_High h2", "max_High h2")) %>% cols_label(Compartment = md("**Compartment**"),
`min_Equal h` = md("*Min.*"), `median_Equal h` = md("*Median*"), `max_Equal h` = md("*Max.*"),
`min_High h1` = md("*Min.*"), `median_High h1` = md("*Median*"), `max_High h1` = md("*Max.*"),
`min_High h2` = md("*Min.*"), `median_High h2` = md("*Median*"), `max_High h2` = md("*Max.*")) %>%
fmt_number(., columns = c(4:12), decimals = 2) %>% data_color(., columns = c(7,
8, 9), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in nutrient flux in the meta-ecosystem when consumers move along an environmental fertility gradient, in the presence of a primary producers recycling rates gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(AlongEFFluxTab, filename = "AlongEFnutflux.tex", path = "../Results/")
AlongEFFluxTab
| Change in nutrient flux in the meta-ecosystem when consumers move along an environmental fertility gradient, in the presence of a primary producers recycling rates gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| Compartment | Equal | High in Donor | High in Recipient | ||||||
| Min. | Median | Max. | Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | |||||||||
| Consumers | 0.26 | 0.26 | 2.45 | −0.91 | 0.26 | 0.26 | 0.26 | 0.26 | 2.77 |
| Producers | 0.00 | 0.00 | 0.00 | 0.28 | 0.28 | 0.28 | −1.00 | −1.00 | −1.00 |
| Ecosystem 2 | |||||||||
| Consumers | −1.93 | −0.27 | 0.51 | −1.93 | −0.28 | 0.51 | −1.93 | −0.27 | 0.71 |
| Producers | −4.00 | −0.17 | 3.44 | −4.00 | −0.17 | 3.44 | −4.00 | −0.17 | 3.44 |
| Meta-ecosystem | |||||||||
| Consumers | −1.18 | −0.09 | 0.76 | −1.18 | −0.09 | 0.33 | −1.18 | −0.09 | 1.07 |
| Producers | −3.78 | −0.02 | 1.69 | −3.75 | 0.22 | 1.70 | −3.94 | −0.70 | 1.69 |
AlongEFProdTab <- AlongEFlrr_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Trophic Productivity") %>% pivot_wider(., names_from = PPRec,
values_from = c(min, median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "PROD_C1rr", Consumers = "PROD_C2rr", Consumers = "PROD_Crr",
Producers = "PROD_P1rr", Producers = "PROD_P2rr", Producers = "PROD_Prr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Producers",
"Consumers")) %>% gt(groupname_col = "Scale") %>% cols_hide("Function") %>%
tab_spanner(label = md("**Equal**"), columns = c("min_Equal h", "median_Equal h",
"max_Equal h")) %>% tab_spanner(label = md("**High in Donor**"),
columns = c("min_High h1", "median_High h1", "max_High h1")) %>% tab_spanner(label = md("**High in Recipient**"),
columns = c("min_High h2", "median_High h2", "max_High h2")) %>% cols_label(Compartment = md("**Compartment**"),
`min_Equal h` = md("*Min.*"), `median_Equal h` = md("*Median*"), `max_Equal h` = md("*Max.*"),
`min_High h1` = md("*Min.*"), `median_High h1` = md("*Median*"), `max_High h1` = md("*Max.*"),
`min_High h2` = md("*Min.*"), `median_High h2` = md("*Median*"), `max_High h2` = md("*Max.*")) %>%
fmt_number(., columns = c(4:12), decimals = 2) %>% data_color(., columns = c(7,
8, 9), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in trophic compartment productivity in the meta-ecosystem when consumers move along an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(AlongEFProdTab, filename = "AlongEFprod.tex", path = "../Results/")
AlongEFProdTab
| Change in trophic compartment productivity in the meta-ecosystem when consumers move along an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| Compartment | Equal | High in Donor | High in Recipient | ||||||
| Min. | Median | Max. | Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | |||||||||
| Consumers | 0.26 | 0.26 | 2.45 | −0.91 | 0.26 | 0.26 | 0.26 | 0.26 | 2.77 |
| Producers | 0.00 | 0.12 | 0.26 | 0.26 | 0.27 | 0.28 | −1.00 | −0.12 | 0.26 |
| Ecosystem 2 | |||||||||
| Consumers | −3.25 | −0.46 | 0.62 | −3.49 | −0.47 | 0.62 | −3.25 | −0.46 | 0.62 |
| Producers | −3.01 | −0.35 | 1.15 | −3.42 | −0.35 | 1.16 | −2.86 | −0.35 | 1.15 |
| Meta-ecosystem | |||||||||
| Consumers | −1.11 | −0.04 | 0.79 | −1.11 | −0.05 | 0.32 | −1.11 | −0.04 | 1.39 |
| Producers | −1.60 | −0.04 | 1.05 | −1.57 | 0.07 | 1.06 | −1.62 | −0.26 | 1.04 |
Note: column headers refer to the direction of the primary producers recycling rate gradient. Equal, equal primary producers recycling rates; High in Donor, primary producers recycling rates higher in ecosystem 1; High in Recipient, primary producers recycling rates higher in ecosystem 2
AgainstEFStockTab <- AgainstEFlrr_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Stock") %>% pivot_wider(., names_from = PPRec, values_from = c(min,
median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "C1rr", Consumers = "C2rr", Consumers = "Crr", Producers = "P1rr",
Producers = "P2rr", Producers = "Prr", Nutrients = "N1rr", Nutrients = "N2rr",
Nutrients = "Nrr")) %>% dplyr::mutate(., Compartment = fct_relevel(Compartment,
"Nutrients", "Producers", "Consumers")) %>% gt(groupname_col = "Scale") %>%
cols_hide("Function") %>% tab_spanner(label = md("**Equal**"), columns = c("min_Equal h",
"median_Equal h", "max_Equal h")) %>% tab_spanner(label = md("**High in Donor**"),
columns = c("min_High h1", "median_High h1", "max_High h1")) %>% tab_spanner(label = md("**High in Recipient**"),
columns = c("min_High h2", "median_High h2", "max_High h2")) %>% cols_label(Compartment = md("**Compartment**"),
`min_Equal h` = md("*Min.*"), `median_Equal h` = md("*Median*"), `max_Equal h` = md("*Max.*"),
`min_High h1` = md("*Min.*"), `median_High h1` = md("*Median*"), `max_High h1` = md("*Max.*"),
`min_High h2` = md("*Min.*"), `median_High h2` = md("*Median*"), `max_High h2` = md("*Max.*")) %>%
fmt_number(., columns = c(4:12), decimals = 2) %>% data_color(., columns = c(7,
8, 9), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in nutrient stock and organic biomass accumulation in the meta-ecosystem when consumers move against an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(AgainstEFStockTab, filename = "AgainstEFbiostock.tex", path = "../Results/")
AgainstEFStockTab
| Change in nutrient stock and organic biomass accumulation in the meta-ecosystem when consumers move against an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| Compartment | Equal | High in Donor | High in Recipient | ||||||
| Min. | Median | Max. | Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | |||||||||
| Consumers | −3.16 | −0.72 | −0.70 | −2.78 | −0.74 | −0.70 | −0.70 | −0.70 | 1.54 |
| Nutrients | −0.70 | −0.16 | 0.00 | −0.70 | 0.09 | 0.28 | −1.00 | −0.86 | −0.70 |
| Producers | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 |
| Ecosystem 2 | |||||||||
| Consumers | −0.80 | 0.17 | 1.25 | −1.10 | 0.16 | 1.25 | −0.69 | 0.17 | 1.25 |
| Nutrients | −1.52 | 0.13 | 1.56 | −1.52 | 0.13 | 1.56 | −1.52 | 0.13 | 1.56 |
| Producers | 0.00 | 0.04 | 1.47 | −1.83 | 0.04 | 1.47 | −1.83 | 0.03 | 1.47 |
| Meta-ecosystem | |||||||||
| Consumers | −1.16 | 0.08 | 0.69 | −1.20 | 0.07 | 2.01 | −0.70 | 0.08 | 2.01 |
| Nutrients | −1.03 | 0.04 | 0.71 | −1.00 | 0.11 | 0.73 | −1.12 | −0.05 | 0.69 |
| Producers | 0.00 | 0.01 | 0.92 | −1.29 | 0.01 | 1.40 | −1.29 | 0.01 | 1.35 |
AgainstEFFluxTab <- AgainstEFlrr_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Nutrient Flux") %>% pivot_wider(., names_from = PPRec,
values_from = c(min, median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "FLUX_C1rr", Consumers = "FLUX_C2rr", Consumers = "FLUX_Crr",
Producers = "FLUX_P1rr", Producers = "FLUX_P2rr", Producers = "FLUX_Prr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Producers",
"Consumers")) %>% gt(groupname_col = "Scale") %>% cols_hide("Function") %>%
tab_spanner(label = md("**Equal**"), columns = c("min_Equal h", "median_Equal h",
"max_Equal h")) %>% tab_spanner(label = md("**High in Donor**"),
columns = c("min_High h1", "median_High h1", "max_High h1")) %>% tab_spanner(label = md("**High in Recipient**"),
columns = c("min_High h2", "median_High h2", "max_High h2")) %>% cols_label(Compartment = md("**Compartment**"),
`min_Equal h` = md("*Min.*"), `median_Equal h` = md("*Median*"), `max_Equal h` = md("*Max.*"),
`min_High h1` = md("*Min.*"), `median_High h1` = md("*Median*"), `max_High h1` = md("*Max.*"),
`min_High h2` = md("*Min.*"), `median_High h2` = md("*Median*"), `max_High h2` = md("*Max.*")) %>%
fmt_number(., columns = c(4:12), decimals = 2) %>% data_color(., columns = c(7,
8, 9), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in nutrient flux in the meta-ecosystem when consumers move against an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(AgainstEFFluxTab, filename = "AgainstEFnutflux.tex", path = "../Results/")
AgainstEFFluxTab
| Change in nutrient flux in the meta-ecosystem when consumers move against an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| Compartment | Equal | High in Donor | High in Recipient | ||||||
| Min. | Median | Max. | Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | |||||||||
| Consumers | −3.16 | −0.72 | −0.70 | −2.78 | −0.74 | −0.70 | −0.70 | −0.70 | 1.54 |
| Producers | 0.00 | 0.00 | 0.00 | 0.28 | 0.28 | 0.28 | −1.00 | −1.00 | −1.00 |
| Ecosystem 2 | |||||||||
| Consumers | −0.80 | 0.17 | 1.25 | −1.10 | 0.16 | 1.25 | −0.69 | 0.17 | 1.25 |
| Producers | −3.75 | 0.08 | 3.57 | −3.75 | 0.08 | 3.57 | −3.75 | 0.08 | 3.57 |
| Meta-ecosystem | |||||||||
| Consumers | −0.86 | 0.07 | 0.64 | −1.35 | 0.07 | 1.15 | −0.70 | 0.08 | 1.15 |
| Producers | −3.49 | 0.01 | 1.70 | −3.34 | 0.26 | 1.71 | −3.71 | −0.52 | 1.69 |
AgainstEFProdTab <- AgainstEFlrr_tables %>% dplyr::ungroup() %>% dplyr::filter(.,
Function == "Trophic Productivity") %>% pivot_wider(., names_from = PPRec,
values_from = c(min, median, max)) %>% dplyr::mutate(., Compartment = fct_recode(Compartment,
Consumers = "PROD_C1rr", Consumers = "PROD_C2rr", Consumers = "PROD_Crr",
Producers = "PROD_P1rr", Producers = "PROD_P2rr", Producers = "PROD_Prr")) %>%
dplyr::mutate(., Compartment = fct_relevel(Compartment, "Producers",
"Consumers")) %>% gt(groupname_col = "Scale") %>% cols_hide("Function") %>%
tab_spanner(label = md("**Equal**"), columns = c("min_Equal h", "median_Equal h",
"max_Equal h")) %>% tab_spanner(label = md("**High in Donor**"),
columns = c("min_High h1", "median_High h1", "max_High h1")) %>% tab_spanner(label = md("**High in Recipient**"),
columns = c("min_High h2", "median_High h2", "max_High h2")) %>% cols_label(Compartment = md("**Compartment**"),
`min_Equal h` = md("*Min.*"), `median_Equal h` = md("*Median*"), `max_Equal h` = md("*Max.*"),
`min_High h1` = md("*Min.*"), `median_High h1` = md("*Median*"), `max_High h1` = md("*Max.*"),
`min_High h2` = md("*Min.*"), `median_High h2` = md("*Median*"), `max_High h2` = md("*Max.*")) %>%
fmt_number(., columns = c(4:12), decimals = 2) %>% data_color(., columns = c(7,
8, 9), colors = col_numeric(palette = "PiYG", domain = c(-1, 1))) %>%
tab_header(title = "Change in trophic compartment productivity in the meta-ecosystem when consumers move against an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model.") %>%
tab_style(style = cell_text(align = "left", size = 18), locations = cells_title())
gtsave(AgainstEFProdTab, filename = "AgainstEFprod.tex", path = "../Results/")
AgainstEFProdTab
| Change in trophic compartment productivity in the meta-ecosystem when consumers move against an environmental fertility gradient, in the presence of a primary producers recycling rate gradient. Values are expressed as median log10 response ratio for 10000 iterations of the model. | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| Compartment | Equal | High in Donor | High in Recipient | ||||||
| Min. | Median | Max. | Min. | Median | Max. | Min. | Median | Max. | |
| Ecosystem 1 | |||||||||
| Consumers | −3.16 | −0.72 | −0.70 | −2.78 | −0.74 | −0.70 | −0.70 | −0.70 | 1.54 |
| Producers | −0.70 | −0.16 | 0.00 | −0.70 | 0.09 | 0.28 | −1.00 | −0.86 | −0.70 |
| Ecosystem 2 | |||||||||
| Consumers | −0.39 | 0.22 | 1.45 | −0.39 | 0.22 | 1.45 | −0.39 | 0.22 | 1.59 |
| Producers | −1.29 | 0.19 | 1.73 | −1.70 | 0.19 | 1.74 | −1.71 | 0.19 | 1.73 |
| Meta-ecosystem | |||||||||
| Consumers | −0.89 | 0.03 | 0.60 | −1.40 | 0.03 | 0.60 | −0.70 | 0.04 | 0.99 |
| Producers | −1.23 | 0.03 | 1.30 | −1.59 | 0.16 | 1.31 | −1.64 | −0.11 | 1.43 |
tictoc::toc()
Total running time:: 5808.416 sec elapsed